diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7203af6..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 shuiyu - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index bf46e5c..91a8e2c 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ 你好呀~这个仓库已经搬到更好用的地方啦。 -为了让代码管理更清爽、多平台同步更省心,我们把原先分散的镜像统一收拢到 -**Gitee** 与 **GitHub** 主仓维护,这里就只保留这张"迁移小纸条"咯。 +为了让代码管理更清爽、多平台同步更省心,我们把原先分散的镜像统一收拢到主仓维护,这里就只保留这张"迁移小纸条"咯。 📦 新家在这里: - Gitee:https://gitee.com/shuiyu1123/codemao-webgl-engine diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js deleted file mode 100644 index 1a0f639..0000000 --- a/benchmark/benchmark.js +++ /dev/null @@ -1,124 +0,0 @@ -// ============================================================ -// 性能对比基准测试 -// 模拟 Heart 解释器 vs JIT 编译器的执行性能 -// ============================================================ - -const ITERATIONS = 100000; - -// 模拟 Heart 解释器的执行方式 -function heartInterpreterSimulated(blocks, runtime) { - let result = 0; - let pc = 0; // program counter - const stack = []; - - while (pc < blocks.length) { - const block = blocks[pc]; - - // 模拟 step_eval_dispatch 的开销 - const blockType = block.type; - const params = block.params; - - // 分发开销:查找 block type - switch (blockType) { - case 'variables_get': - // 获取变量值 - result = runtime.getVariable(params.VAR); - break; - case 'variables_set': - runtime.setVariable(params.VAR, result); - break; - case 'math_arithmetic': - const a = runtime.getVariable(params.A); - const b = runtime.getVariable(params.B); - switch (params.OP) { - case 'ADD': result = a + b; break; - case 'MINUS': result = a - b; break; - case 'MULTIPLY': result = a * b; break; - case 'DIVIDE': result = a / b; break; - } - break; - case 'logic_compare': - const la = runtime.getVariable(params.A); - const lb = runtime.getVariable(params.B); - switch (params.OP) { - case 'EQ': result = la == lb; break; - case 'LT': result = la < lb; break; - case 'GT': result = la > lb; break; - } - break; - case 'repeat_n_times': - // 解释器需要为每次迭代管理状态 - for (let i = 0; i < params.TIMES; i++) { - // 模拟 yield 检查 - if (i % 1000 === 0) { - // 解释器的 yield 开销 - stack.push({ pc, frame: i }); - stack.pop(); - } - result += i; - } - break; - } - - // 模拟 next_block 查找开销 - pc++; - } - - return result; -} - -// JIT 编译后的执行方式 -function jitCompiled(runtime) { - let result = 0; - - // 直接执行,无 dispatch 开销 - for (let i = 0; i < ITERATIONS; i++) { - result = runtime.getVariable('x'); - result = result + runtime.getVariable('y'); - result = result * 2; - runtime.setVariable('x', result); - } - - return result; -} - -// 运行基准测试 -function runBenchmark() { - const runtime = { - vars: { x: 1, y: 2, n: 1000 }, - getVariable(name) { return this.vars[name] || 0; }, - setVariable(name, val) { this.vars[name] = val; } - }; - - // 构造积木序列 - const blocks = []; - for (let i = 0; i < ITERATIONS; i++) { - blocks.push( - { type: 'variables_get', params: { VAR: 'x' } }, - { type: 'math_arithmetic', params: { A: 'x', B: 'y', OP: 'ADD' } }, - { type: 'variables_set', params: { VAR: 'x' } } - ); - } - - // 测试解释器 - console.time('Heart Interpreter'); - const interpResult = heartInterpreterSimulated(blocks, { ...runtime }); - console.timeEnd('Heart Interpreter'); - - // 重置 runtime - runtime.vars.x = 1; - - // 测试 JIT - console.time('JIT Compiled'); - const jitResult = jitCompiled({ ...runtime }); - console.timeEnd('JIT Compiled'); - - return { interpResult, jitResult }; -} - -// 导出 -if (typeof module !== 'undefined') { - module.exports = { runBenchmark }; -} else { - window.runBenchmark = runBenchmark; -} diff --git a/docs/decompile-progress.md b/docs/decompile-progress.md deleted file mode 100644 index bd59a30..0000000 --- a/docs/decompile-progress.md +++ /dev/null @@ -1,27 +0,0 @@ -# 反编译进度 - -新仓库: https://gitee.com/shuiyu1123/codemao-heart-source (私有) - -## 已还原的核心模块 - -| 模块 | 文件 | 状态 | -|------|------|------| -| RegistryImpl | `src/heart/Registry.js` | ✅ 完成 | -| BlockPredicatesImpl | `src/heart/BlockPredicates.js` | ✅ 完成 | -| CompilerImpl | `src/heart/Compiler.js` | ✅ 完成 | -| InterpreterImpl | `src/heart/Interpreter.js` | ✅ 完成 (完整) | -| RuntimeManagerImpl | `src/heart/RuntimeManager.js` | ✅ 完成 | - -## 完整文档 - -- `docs/player-source-analysis.md` - 1.8MB bundle 完整架构分析 -- `docs/module-map.md` - webpack 模块映射表 -- `docs/domain-functions.md` - 80+ 积木实现清单 -- `TASKS.md` - 反编译任务清单 - -## 下一步 - -1. 还原 RuntimeDataImpl, BlockPoolImpl, EventBus -2. 还原 Domain Functions (积木实际实现) -3. 还原渲染层 (PIXI.js 集成) -4. JIT 编译器与还原的 Interpreter 对接 diff --git a/docs/player-source-analysis.md b/docs/player-source-analysis.md deleted file mode 100644 index 682301c..0000000 --- a/docs/player-source-analysis.md +++ /dev/null @@ -1,442 +0,0 @@ -# 编程猫 Player v2.js 源码架构分析 - -> 从 `https://kn-cdn.codemao.cn/kn/build/player_v2.2d180c468559fbd29528.js` 提取 -> 文件大小: 1.8MB, webpack 打包, 125 个模块 - -## 1. 整体架构 - -``` -┌─────────────────────────────────────────────────────────┐ -│ React UI 层 │ -│ Theatre (舞台), Ranking (排行榜), Scanner (扫码), │ -│ FaceDetect, VirtualKeyboard, Dialog │ -├─────────────────────────────────────────────────────────┤ -│ Redux + Saga 状态管理 │ -│ theatre_states, run_states, cloud_states, │ -│ variable_states, userinfo_state, ai_states │ -├─────────────────────────────────────────────────────────┤ -│ Heart 运行时引擎 │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │Compiler │ │Interpreter│ │Registry │ │RuntimeMgr│ │ -│ │(编译积木) │ │(执行积木) │ │(注册积木) │ │(管理实体) │ │ -│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ -├─────────────────────────────────────────────────────────┤ -│ PIXI.js 渲染层 (WebGL) │ -│ Application, Container, Sprite, Graphics, │ -│ Text, Filter, Ticker, InteractionManager │ -├─────────────────────────────────────────────────────────┤ -│ 底层服务 │ -│ Audio (Howler.js), Physics, MIDI, Cloud Variables, │ -│ Face Detection, QR Scanner, WebSocket │ -└─────────────────────────────────────────────────────────┘ -``` - -## 2. Heart 运行时核心类 - -### 2.1 `Heart` (入口) -```javascript -// e.Heart = _ -// 创建: new_heart(config) -// 方法: -// get_runtime_manager() → RuntimeManager -// get_runtime_data() → RuntimeData -// get_registry() → Registry -// get_compiler() → Compiler -// get_event_bus() → EventBus -// get_util() → BlockUtil -// lint(entities, linters) → { errors } -``` - -### 2.2 `RegistryImpl` (积木注册表) -```javascript -class RegistryImpl { - // 存储 - domain_function = {} // 积木实现函数 - domain_function_index = {} // 函数索引 - respond = {} // 响应器 - lifetime_respond = {} // 生命周期响应器 - action_types = {} // 动作类型 - meta_restart_when_finished = {} - meta_finish_out_of_run_group = {} - - // 方法 - register({ namespace, id, domain_function, respond, metadata }) - register_action_type({ namespace, id, statefulness }) - register_linter(linter) - has_responder_type(type) → boolean - has_lifetime_responder_type(type) → boolean - get_action_type(namespaced_id) → ActionSpec - get_domain_functions() → { [name]: Function } -} -``` - -### 2.3 `CompilerImpl` / `OptiCompilerImpl` (编译器) -```javascript -class CompilerImpl { - // 将 workspace_json 编译为可执行的积木树 - compile(entity, procedures, is_clone) → compiled_block_map - compile_from_workspace_json(blocksJSON, entity_id, is_clone) → { - procedures: { [name]: block }, - compiled_block_map: { [id]: block } - } -} - -class OptiCompilerImpl { - // 优化编译器 - 生成更高效的 AST - compile(entity, procedures, is_clone) → compiled_program -} -``` - -### 2.4 `InterpreterImpl` (解释器) ⭐ 性能瓶颈 -```javascript -class InterpreterImpl { - // 核心状态 - root_block // 根积木 - current_block // 当前执行的积木 - current_frame // 当前栈帧 - program_state // 程序状态 - n_warp_iterations // warp 迭代计数 - n_procedure_calls // 过程调用计数 - - // 核心方法 - step() → StepResult // 执行一步 ← 这是最热的路径 - do_step() → StepResult // 内部执行 - - // do_step 的执行流程: - // 1. get_stack_frame() - 获取当前栈帧 - // 2. step_eval_dispatch() - 根据块类型分发 - // 3. 处理各种结果类型: - // - Yield → 返回 yielding - // - Finish → 返回 finished - // - EvalArg → 求值参数 - // - EvalChild → 求值子块 - // - EvalTell → 求值 tell 块 - // - EvalWarp → 求值 warp 块 - // - ProcedureCall → 调用过程 - // - ProcedureReturn → 过程返回 - // 4. go_next_or_parent() - 导航到下一个块或父块 - // 5. must_yield() - 检查是否需要让出 - - // step_eval_dispatch 分发逻辑: - step_eval_dispatch() { - if (block.disabled) return skip - if (is.responder_block(block)) return eval_child_or_done - if (is.event_block(block)) return eval_event - if (is.cond_block(block)) return eval_cond - if (is.domain_block(block)) return eval_domain - if (is.break(block)) return Break - if (is.repeat_forever(block)) return eval_repeat_forever - if (is.repeat_n_times(block)) return eval_repeat_n_times - if (is.repeat_forever_until(block)) return eval_repeat_forever_until - if (is.wait_until(block)) return eval_wait_until - if (is.procedures_callreturn(block)) return eval_procedures_callreturn - if (is.procedures_defnoreturn(block)) return eval_procedures_defnoreturn - if (is.procedures_parameter(block)) return eval_procedures_parameter - if (is.procedures_return_value(block)) return eval_procedures_return_value - if (is.procedures_callnoreturn(block)) return eval_procedures_callnoreturn - if (is.sync_tell(block)) return eval_sync_tell - if (is.async_tell(block)) return eval_async_tell - if (is.warp(block)) return eval_warp - throw unknown_block_type - } -} -``` - -### 2.5 `RuntimeManagerImpl` (运行时管理器) -```javascript -class RuntimeManagerImpl { - // 实体管理 - create_entity_instance(entity_id, typeclass_id) - create_singleton_entity_instances() - destruct_entity(entity_id) - get_entity_state(entity_id) → EntityState - - // 解释器管理 - spawn_interpreter(config) - spawn_async_tell_interpreter(config) - remove_interpreter(interpreter_id) - add_interpreter(interpreter) - get_interpreter_ids() → string[] - get_n_interpreters() → number - - // 执行 - run() // 开始执行 - stop() // 停止 - update() // 每帧更新 ← 调用所有 interpreter.step() - - // 变量 - get_variable(name, entity_id, interpreter_id) - set_variable(name, value, entity_id, interpreter_id) - set_variable_specs(specs) - - // 线程控制 - thread_wait(entity_id, interpreter_id, ms) - get_thread_lock(entity_id, interpreter_id) → ThreadLock - - // 广播 - broadcaster_sending_message(entity_id, message, is_blocking) - - // 动作 - send_action({ id, namespace, parameters, value }) -} -``` - -### 2.6 `RuntimeDataImpl` (运行时数据) -```javascript -class RuntimeDataImpl { - // 变量和列表 - clone_id_2_original_id(clone_id) → original_id - get_arbitrary_data(key) → any - set_arbitrary_data(key, value) - is_running() → boolean - - // 事件总线 - variable_update → EventBus - list_update → EventBus - entity_variable_update → EventBus - entity_list_update → EventBus -} -``` - -### 2.7 `BlockPredicatesImpl` (积木谓词) -```javascript -class BlockPredicatesImpl { - // 判断积木类型 - hat_block_type(type) → boolean - responder_block(block) → boolean - responder_type(type) → boolean - lifetime_responder_type(type) → boolean - event_block(block) → boolean - domain_block(block) → boolean - loop_block(block) → boolean - cond_block(block) → boolean - proc_block(block) → boolean - special_block(block) → boolean - - // 具体积木判断 - controls_if(block) → boolean - repeat_n_times(block) → boolean - repeat_forever(block) → boolean - repeat_forever_until(block) → boolean - wait_until(block) → boolean - break(block) → boolean - procedures_defnoreturn(block) → boolean - procedures_callreturn(block) → boolean - async_tell(block) → boolean - sync_tell(block) → boolean - warp(block) → boolean - mirror(block) → boolean - start_on_click(block) → boolean -} -``` - -## 3. Domain Functions (积木实现) - -积木的实际功能通过 domain functions 注册到 Registry: - -### 3.1 变量操作 -```javascript -variables_get(t, e, n) // 获取变量 -variables_set(t, e, n) // 设置变量 -change_variable(t, e, n) // 改变变量 -``` - -### 3.2 列表操作 -```javascript -lists_append(t, e, n) // 添加到列表 -lists_delete(t, e, n) // 删除列表项 -lists_insert_value(t, e, n) // 插入值 -lists_replace(t, e, n) // 替换列表项 -lists_get_value(t, e, n) // 获取列表值 -lists_length(t, e, n) // 列表长度 -lists_index_of(t, e, n) // 查找索引 -lists_copy(t, e, n) // 复制列表 -lists_is_exist(t, e, n) // 检查值是否存在 -``` - -### 3.3 运动 -```javascript -self_move_to(t, e, n) // 移动到 -self_set_position(t, e, n) // 设置位置 -self_change_coordinate(t, e, n)// 改变坐标 -self_glide_to(t, e, n) // 滑行到 -self_glide_coordinate(t, e, n) // 滑行到坐标 -self_point_towards(t, e, n) // 面向 -self_face_to(t, e, n) // 面向角色 -self_bounce_off_edge(t, e, n) // 碰到边缘反弹 -``` - -### 3.4 外观 -```javascript -show(t, e, n) // 显示 -hide(t, e, n) // 隐藏 -self_set_effect_2(t, e, n) // 设置特效 -self_change_effect_3(t, e, n) // 改变特效 -``` - -### 3.5 声音 -```javascript -play_audio(t, e, n) // 播放声音 -play_audio_and_wait(t, e, n) // 播放并等待 -play_audio_2(t, e, n) // 播放声音 v2 -play_audio_and_wait_2(t, e, n)// 播放并等待 v2 -stop_audio_2(t, e, n) // 停止声音 -play_words_audio(t, e, n) // 朗读文字 -play_words_audio_wait(t, e, n)// 朗读并等待 -``` - -### 3.6 事件 -```javascript -self_broadcast(t, e, n) // 广播消息 -self_broadcast_and_wait(t, e, n) // 广播并等待 -self_listen(t, e, n) // 当收到消息 -when(t, e, n) // 当...时 -start_on_click(t, e, n) // 当点击 -``` - -### 3.7 控制 -```javascript -wait(t, e, n) // 等待 -repeat_n_times // 重复 N 次 -repeat_forever // 永久重复 -repeat_forever_until // 重复直到 -terminate(t, e, n) // 终止 -stop(t, e, n) // 停止 -restart(t, e, n) // 重新开始 -destruct(t, e, n) // 销毁 -``` - -### 3.8 逻辑和数学 -```javascript -logic_compare(t, e, n) // 比较 -logic_operation(t, e, n) // 逻辑运算 -logic_negate(t, e, n) // 逻辑非 -logic_boolean(t, e, n) // 布尔值 -math_arithmetic(t, e, n) // 四则运算 -math_single(t, e, n) // 单目运算 -math_round(t, e, n) // 取整 -math_modulo(t, e, n) // 取余 -random(t, e, n) // 随机数 -calculate(t, e, n) // 计算表达式 -``` - -### 3.9 文本 -```javascript -text_join(t, e, n) // 拼接 -text_length(t, e, n) // 长度 -text_indexof(t, e, n) // 查找 -text_contain(t, e, n) // 包含 -text_select(t, e, n) // 截取 -text_split(t, e, n) // 分割 -text_append(t, e, n) // 追加 -text_implicit(t, e, n) // 隐式转换 -``` - -### 3.10 询问/语音 -```javascript -self_ask_listen(t, e, n) // 询问并等待回答 -voice_recognition(t, e, n) // 语音识别 -self_ask_record(t, e, n) // 询问并录音 -get_voice_answer(t, e, n) // 获取语音回答 -enable_voice_detection(t, e, n) // 启用声音侦测 -get_voice_volume(t, e, n) // 获取音量 -``` - -### 3.11 时间 -```javascript -get_time(t, e, n) // 获取时间 -get_cur_frames(t, e, n) // 获取当前帧数 -reset_timer(t, e, n) // 重置计时器 -get_timer(t, e, n) // 获取计时器 -``` - -## 4. BCM 加载流程 - -``` -1. player.codemao.cn/t/{work_id} 加载 -2. fetch_bcm_request action 触发 -3. API: GET https://api.codemao.cn/api/work/info/{work_id} -4. 获取 BCM 数据 (workspace_json) -5. Heart.Compiler.compile(workspace_json) → compiled_block_map -6. RuntimeManager.load(entities) → 加载所有实体 -7. RuntimeManager.run() → 开始执行 -8. requestAnimationFrame 循环 → RuntimeManager.update() - → 每个 Interpreter.step() -``` - -## 5. 渲染层 (PIXI.js) - -PIXI.js 版本: 6.0.4 - -```javascript -// PIXI 核心组件 -PIXI.Application // 应用 (包含 ticker, stage, renderer) -PIXI.Container // 容器 (display object 树) -PIXI.Sprite // 精灵 -PIXI.Graphics // 矢量图形 -PIXI.Text // 文字 -PIXI.Filter // 滤镜 (blur, twist, pixelate, ASCII 等) -PIXI.Ticker // 帧循环 (requestAnimationFrame) -PIXI.InteractionManager // 交互管理 (点击, 拖拽) -PIXI.Texture // 纹理 -PIXI.BaseTexture // 基础纹理 -PIXI.RenderTexture // 渲染纹理 - -// 着色器 -// 顶点着色器: 标准 2D 变换 -// 片段着色器: 纹理采样 + 颜色混合 -// 支持 WebGL 1.0 和 2.0 -``` - -## 6. 性能瓶颈分析 - -| 组件 | 耗时占比 | 瓶颈原因 | -|------|---------|---------| -| Interpreter.do_step() | ~60% | 逐条解释, dispatch 开销, 栈帧管理 | -| Interpreter.step_eval_dispatch() | ~25% | 大量 if-else 类型判断 | -| RuntimeManager.update() | ~10% | 遍历所有 interpreter | -| PIXI.js 渲染 | ~5% | 已经是 WebGL, 高效 | - -## 7. JIT 加速切入点 - -### 7.1 直接替换 Interpreter.step() -```javascript -// 找到 Interpreter 实例 -const rm = heart.get_runtime_manager(); -// rm 内部维护 interpreter 列表 -// 替换每个 interpreter 的 step 方法 -``` - -### 7.2 拦截 Compiler.compile() -```javascript -// 在编译阶段介入 -// 将 compiled_block_map 转为原生 JS -// 绕过 Interpreter 的 step-by-step 执行 -``` - -### 7.3 优化 step_eval_dispatch() -```javascript -// 将 if-else 链改为 lookup table -// 预编译 block type → handler 映射 -// 减少每次 dispatch 的开销 -``` - -## 8. 关键代码位置 (webpack 模块) - -| 功能 | 模块特征 | -|------|---------| -| Heart 入口 | `new_heart`, `e.Heart` | -| Registry | `e.RegistryImpl`, `has_responder_type` | -| Compiler | `e.CompilerImpl`, `compile_from_workspace_json` | -| OptiCompiler | `e.OptiCompilerImpl` | -| Interpreter | `do_step`, `step_eval_dispatch` | -| RuntimeManager | `e.RuntimeManagerImpl`, `get_runtime_manager` | -| BlockPredicates | `e.BlockPredicatesImpl`, `repeat_forever` | -| BlockProvider | `e.BasicBlockProviderFactoryImpl` | -| Domain Functions | `get_domain_functions`, 各种 `self_*`, `lists_*` | -| PIXI 渲染 | `PIXI.Application`, `lvVU` 模块 | -| 音频 | `AudioPlayer`, `Howl`, MIDI | -| 物理 | `physics2_*` | -| Redux Store | `dqaR` 模块, `ALsR` | -| Theatre | `ypP6` 模块, `ft` 组件 | -| Cloud | `qVl3` 模块, BCM 加载 | diff --git a/userscript/codemao-canvas2webgl.user.js b/userscript/codemao-canvas2webgl.user.js deleted file mode 100644 index 90ee672..0000000 --- a/userscript/codemao-canvas2webgl.user.js +++ /dev/null @@ -1,1017 +0,0 @@ -// ==UserScript== -// @name 编程猫 Canvas→WebGL 转译加速器 [已废弃] -// @namespace https://gitee.com/shuiyu1123/codemao-webgl-engine -// @version 2.0.0-DEPRECATED -// @description [已废弃] 此方案对 Kitten N 无效,因渲染层已是 PIXI.js (WebGL)。请使用 codemao-heart-jit.user.js -// @author codemao-webgl-engine -// @match https://player.codemao.cn/* -// @match https://shequ.codemao.cn/* -// @match https://kitten.codemao.cn/* -// @match https://ide.codemao.cn/* -// @run-at document-start -// @grant none -// @license MIT -// ==/UserScript== - -/** - * ⚠️ 此脚本已废弃 ⚠️ - * - * 经过分析编程猫 player_v2.js 源码,发现 Kitten N 作品的渲染层 - * 已经是 PIXI.js (WebGL),Canvas 2D 拦截方案完全无效。 - * - * 请使用新方案:codemao-heart-jit.user.js - * 新方案通过 JIT 编译 Heart 运行时的积木 AST 为原生 JS, - * 绕过解释器的逐条执行循环,实现 10-50x 性能提升。 - * - * 详见 README.md - */ - -(function () { - 'use strict'; - - // ================================================================ - // 核心:Canvas 2D → WebGL 转译器 - // 拦截 getContext('2d'),返回一个 Proxy 对象 - // 所有 Canvas 2D API 调用内部转为 WebGL 操作 - // ================================================================ - - const TAG = '🐱[Canvas→WebGL]'; - const log = (...a) => console.log(TAG, ...a); - - // ------------------------------------------------ - // WebGL 渲染核心 - // ------------------------------------------------ - class GLRenderer { - constructor(canvas) { - this.canvas = canvas; - this.gl = canvas.getContext('webgl2', { - alpha: true, - antialias: false, - premultipliedAlpha: true, - preserveDrawingBuffer: false, - powerPreference: 'high-performance', - desynchronized: true, - failIfMajorPerformanceCaveat: false - }) || canvas.getContext('webgl', { - alpha: true, - antialias: false, - powerPreference: 'high-performance', - desynchronized: true, - failIfMajorPerformanceCaveat: false - }); - - if (!this.gl) { - throw new Error('WebGL not available'); - } - - this.gl.enable(this.gl.BLEND); - this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); - - // 批量渲染缓冲 - this.maxQuads = 4096; - this.vertexData = new Float32Array(this.maxQuads * 6 * 9); // 6 verts/quad, 9 floats/vert - this.quadCount = 0; - this.currentTexture = null; - this.textureSlot = 0; - this.drawCalls = 0; - - // 变换矩阵栈 - this.transformStack = []; - this.matrix = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]); - - // 投影矩阵 - this._updateProjection(); - - // 纹理缓存 - this.textureCache = new Map(); - this.textureSlots = new Array(8).fill(null); - - // 初始化着色器和缓冲 - this._initShaders(); - this._initBuffers(); - - // 离屏 Canvas 用于文字渲染 - this._textCanvas = document.createElement('canvas'); - this._textCtx = this._textCanvas.getContext('2d'); - - log('✅ WebGL 渲染器初始化成功'); - } - - _updateProjection() { - const w = this.canvas.width; - const h = this.canvas.height; - this.projection = new Float32Array([2 / w, 0, 0, 0, -2 / h, 0, -1, 1, 1]); - } - - _initShaders() { - const gl = this.gl; - - const vsSrc = ` - attribute vec2 a_pos; - attribute vec2 a_uv; - attribute vec4 a_color; - attribute float a_texId; - uniform mat3 u_proj; - varying vec2 v_uv; - varying vec4 v_color; - varying float v_texId; - void main() { - vec3 p = u_proj * vec3(a_pos, 1.0); - gl_Position = vec4(p.xy, 0.0, 1.0); - v_uv = a_uv; - v_color = a_color; - v_texId = a_texId; - } - `; - - const fsSrc = ` - precision mediump float; - varying vec2 v_uv; - varying vec4 v_color; - varying float v_texId; - uniform sampler2D u_tex0; - uniform sampler2D u_tex1; - uniform sampler2D u_tex2; - uniform sampler2D u_tex3; - uniform sampler2D u_tex4; - uniform sampler2D u_tex5; - uniform sampler2D u_tex6; - uniform sampler2D u_tex7; - uniform bool u_useTex; - void main() { - if (u_useTex) { - vec4 tc; - int i = int(v_texId); - if (i==0) tc = texture2D(u_tex0, v_uv); - else if (i==1) tc = texture2D(u_tex1, v_uv); - else if (i==2) tc = texture2D(u_tex2, v_uv); - else if (i==3) tc = texture2D(u_tex3, v_uv); - else if (i==4) tc = texture2D(u_tex4, v_uv); - else if (i==5) tc = texture2D(u_tex5, v_uv); - else if (i==6) tc = texture2D(u_tex6, v_uv); - else tc = texture2D(u_tex7, v_uv); - gl_FragColor = tc * v_color; - } else { - gl_FragColor = v_color; - } - if (gl_FragColor.a < 0.004) discard; - } - `; - - const compile = (src, type) => { - const s = gl.createShader(type); - gl.shaderSource(s, src); - gl.compileShader(s); - if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { - console.error(gl.getShaderInfoLog(s)); - return null; - } - return s; - }; - - const vs = compile(vsSrc, gl.VERTEX_SHADER); - const fs = compile(fsSrc, gl.FRAGMENT_SHADER); - this.program = gl.createProgram(); - gl.attachShader(this.program, vs); - gl.attachShader(this.program, fs); - gl.linkProgram(this.program); - - if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { - console.error(gl.getProgramInfoLog(this.program)); - } - - gl.useProgram(this.program); - - // 获取 locations - this.loc = { - a_pos: gl.getAttribLocation(this.program, 'a_pos'), - a_uv: gl.getAttribLocation(this.program, 'a_uv'), - a_color: gl.getAttribLocation(this.program, 'a_color'), - a_texId: gl.getAttribLocation(this.program, 'a_texId'), - u_proj: gl.getUniformLocation(this.program, 'u_proj'), - u_useTex: gl.getUniformLocation(this.program, 'u_useTex'), - u_tex: [] - }; - - for (let i = 0; i < 8; i++) { - this.loc.u_tex.push(gl.getUniformLocation(this.program, `u_tex${i}`)); - } - } - - _initBuffers() { - const gl = this.gl; - this.vbo = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexData.byteLength, gl.DYNAMIC_DRAW); - - const stride = 9 * 4; - gl.enableVertexAttribArray(this.loc.a_pos); - gl.vertexAttribPointer(this.loc.a_pos, 2, gl.FLOAT, false, stride, 0); - gl.enableVertexAttribArray(this.loc.a_uv); - gl.vertexAttribPointer(this.loc.a_uv, 2, gl.FLOAT, false, stride, 8); - gl.enableVertexAttribArray(this.loc.a_color); - gl.vertexAttribPointer(this.loc.a_color, 4, gl.FLOAT, false, stride, 16); - if (this.loc.a_texId >= 0) { - gl.enableVertexAttribArray(this.loc.a_texId); - gl.vertexAttribPointer(this.loc.a_texId, 1, gl.FLOAT, false, stride, 32); - } - } - - resize(w, h) { - this.canvas.width = w; - this.canvas.height = h; - this.gl.viewport(0, 0, w, h); - this._updateProjection(); - } - - clear(r = 0, g = 0, b = 0, a = 0) { - const gl = this.gl; - gl.clearColor(r, g, b, a); - gl.clear(gl.COLOR_BUFFER_BIT); - } - - // 变换操作 - save() { - this.transformStack.push(new Float32Array(this.matrix)); - } - - restore() { - if (this.transformStack.length) { - this.matrix.set(this.transformStack.pop()); - } - } - - translate(tx, ty) { - const m = this.matrix; - m[6] += m[0] * tx + m[3] * ty; - m[7] += m[1] * tx + m[4] * ty; - } - - rotate(angle) { - const c = Math.cos(angle), s = Math.sin(angle); - const m = this.matrix; - const [a, b, d, e] = [m[0], m[1], m[3], m[4]]; - m[0] = a * c + d * s; m[1] = b * c + e * s; - m[3] = d * c - a * s; m[4] = e * c - b * s; - } - - scale(sx, sy) { - sy = sy ?? sx; - const m = this.matrix; - m[0] *= sx; m[1] *= sx; m[3] *= sy; m[4] *= sy; - } - - transform(a, b, c, d, e, f) { - const m = this.matrix; - const [m0, m1, m3, m4, m6, m7] = [m[0], m[1], m[3], m[4], m[6], m[7]]; - m[0] = m0 * a + m3 * b; m[1] = m1 * a + m4 * b; - m[3] = m0 * c + m3 * d; m[4] = m1 * c + m4 * d; - m[6] = m0 * e + m3 * f + m6; m[7] = m1 * e + m4 * f + m7; - } - - setTransform(a, b, c, d, e, f) { - this.matrix.set([a, b, 0, c, d, 0, e, f, 1]); - } - - resetTransform() { - this.matrix.set([1, 0, 0, 0, 1, 0, 0, 0, 1]); - } - - // 生成四边形顶点 - _makeQuad(x, y, w, h, u0 = 0, v0 = 0, u1 = 1, v1 = 1, r = 1, g = 1, b = 1, a = 1) { - const m = this.matrix; - const p = this.projection; - - const corners = [ - [x, y], [x + w, y], [x, y + h], - [x + w, y], [x + w, y + h], [x, y + h] - ]; - const uvs = [ - [u0, v0], [u1, v0], [u0, v1], - [u1, v0], [u1, v1], [u0, v1] - ]; - - const verts = new Float32Array(54); // 6 * 9 - for (let i = 0; i < 6; i++) { - const [cx, cy] = corners[i]; - const wx = m[0] * cx + m[3] * cy + m[6]; - const wy = m[1] * cx + m[4] * cy + m[7]; - const off = i * 9; - verts[off] = p[0] * wx + p[6]; - verts[off + 1] = p[4] * wy + p[7]; - verts[off + 2] = uvs[i][0]; - verts[off + 3] = uvs[i][1]; - verts[off + 4] = r; verts[off + 5] = g; - verts[off + 6] = b; verts[off + 7] = a; - verts[off + 8] = 0; - } - return verts; - } - - // 添加四边形到批次 - addQuad(verts, texture = null) { - if (this.quadCount >= this.maxQuads || (texture && texture !== this.currentTexture && this.textureSlot >= 7)) { - this.flush(); - } - - if (texture) { - let slot = this.textureSlots.indexOf(texture); - if (slot === -1) { - slot = this.textureSlot++; - this.textureSlots[slot] = texture; - } - for (let i = 0; i < 6; i++) { - verts[i * 9 + 8] = slot; - } - } - - const offset = this.quadCount * 54; - this.vertexData.set(verts, offset); - this.quadCount++; - this.currentTexture = texture; - } - - // 提交绘制 - flush() { - if (this.quadCount === 0) return; - const gl = this.gl; - - gl.useProgram(this.program); - gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, - this.vertexData.subarray(0, this.quadCount * 54)); - - gl.uniformMatrix3fv(this.loc.u_proj, false, this.projection); - - const useTex = this.textureSlot > 0; - gl.uniform1i(this.loc.u_useTex, useTex); - - if (useTex) { - for (let i = 0; i < this.textureSlot; i++) { - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, this.textureSlots[i]); - gl.uniform1i(this.loc.u_tex[i], i); - } - } - - const stride = 9 * 4; - gl.vertexAttribPointer(this.loc.a_pos, 2, gl.FLOAT, false, stride, 0); - gl.vertexAttribPointer(this.loc.a_uv, 2, gl.FLOAT, false, stride, 8); - gl.vertexAttribPointer(this.loc.a_color, 4, gl.FLOAT, false, stride, 16); - if (this.loc.a_texId >= 0) gl.vertexAttribPointer(this.loc.a_texId, 1, gl.FLOAT, false, stride, 32); - - gl.drawArrays(gl.TRIANGLES, 0, this.quadCount * 6); - this.drawCalls++; - - this.quadCount = 0; - this.textureSlot = 0; - this.textureSlots.fill(null); - this.currentTexture = null; - } - - // 获取或创建纹理 - getTexture(source) { - if (this.textureCache.has(source)) { - return this.textureCache.get(source); - } - const gl = this.gl; - const tex = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - - try { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source); - } catch (e) { - // source 可能还没加载完 - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, - new Uint8Array([255, 255, 255, 255])); - if (source.complete === false || source.readyState < 2) { - const onload = () => { - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source); - source.removeEventListener('load', onload); - }; - source.addEventListener('load', onload); - } - } - - this.textureCache.set(source, tex); - return tex; - } - - // 从颜色值创建 1x1 纯色纹理 - colorTex(r, g, b, a) { - const key = `${r},${g},${b},${a}`; - if (this.textureCache.has(key)) return this.textureCache.get(key); - const gl = this.gl; - const tex = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, - new Uint8Array([r * 255 | 0, g * 255 | 0, b * 255 | 0, a * 255 | 0])); - this.textureCache.set(key, tex); - return tex; - } - - beginFrame() { - this.quadCount = 0; - this.textureSlot = 0; - this.textureSlots.fill(null); - this.drawCalls = 0; - this.transformStack.length = 0; - this.matrix.set([1, 0, 0, 0, 1, 0, 0, 0, 1]); - } - - endFrame() { - this.flush(); - } - } - - // ------------------------------------------------ - // 颜色解析 - // ------------------------------------------------ - function parseColor(color) { - if (!color) return { r: 0, g: 0, b: 0, a: 1 }; - if (typeof color !== 'string') return { r: 0, g: 0, b: 0, a: 1 }; - - if (color.startsWith('#')) { - let h = color.slice(1); - if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; - return { - r: parseInt(h.substring(0, 2), 16) / 255, - g: parseInt(h.substring(2, 4), 16) / 255, - b: parseInt(h.substring(4, 6), 16) / 255, - a: h.length >= 8 ? parseInt(h.substring(6, 8), 16) / 255 : 1 - }; - } - - const m = color.match(/([\d.]+)/g); - if (m) { - return { - r: parseInt(m[0]) / 255, - g: parseInt(m[1]) / 255, - b: parseInt(m[2]) / 255, - a: m[3] !== undefined ? parseFloat(m[3]) : 1 - }; - } - - return { r: 0, g: 0, b: 0, a: 1 }; - } - - // ------------------------------------------------ - // Canvas 2D API 代理(转译层) - // ------------------------------------------------ - class Canvas2DProxy { - constructor(canvas, renderer) { - this._canvas = canvas; - this._gl = renderer; - this._state = { - fillStyle: '#000000', - strokeStyle: '#000000', - lineWidth: 1, - globalAlpha: 1, - globalCompositeOperation: 'source-over', - font: '10px sans-serif', - textAlign: 'start', - textBaseline: 'alphabetic', - shadowBlur: 0, - shadowColor: 'rgba(0,0,0,0)', - shadowOffsetX: 0, - shadowOffsetY: 0, - imageSmoothingEnabled: true, - lineCap: 'butt', - lineJoin: 'miter', - miterLimit: 10, - lineDashOffset: 0 - }; - this._path = []; - this._currentPath = []; - } - - get canvas() { return this._canvas; } - - // 属性代理 - get fillStyle() { return this._state.fillStyle; } - set fillStyle(v) { this._state.fillStyle = v; } - get strokeStyle() { return this._state.strokeStyle; } - set strokeStyle(v) { this._state.strokeStyle = v; } - get lineWidth() { return this._state.lineWidth; } - set lineWidth(v) { this._state.lineWidth = v; } - get globalAlpha() { return this._state.globalAlpha; } - set globalAlpha(v) { this._state.globalAlpha = v; } - get globalCompositeOperation() { return this._state.globalCompositeOperation; } - set globalCompositeOperation(v) { this._state.globalCompositeOperation = v; } - get font() { return this._state.font; } - set font(v) { this._state.font = v; } - get textAlign() { return this._state.textAlign; } - set textAlign(v) { this._state.textAlign = v; } - get textBaseline() { return this._state.textBaseline; } - set textBaseline(v) { this._state.textBaseline = v; } - get shadowBlur() { return this._state.shadowBlur; } - set shadowBlur(v) { this._state.shadowBlur = v; } - get shadowColor() { return this._state.shadowColor; } - set shadowColor(v) { this._state.shadowColor = v; } - get shadowOffsetX() { return this._state.shadowOffsetX; } - set shadowOffsetX(v) { this._state.shadowOffsetX = v; } - get shadowOffsetY() { return this._state.shadowOffsetY; } - set shadowOffsetY(v) { this._state.shadowOffsetY = v; } - get imageSmoothingEnabled() { return this._state.imageSmoothingEnabled; } - set imageSmoothingEnabled(v) { this._state.imageSmoothingEnabled = v; } - get lineCap() { return this._state.lineCap; } - set lineCap(v) { this._state.lineCap = v; } - get lineJoin() { return this._state.lineJoin; } - set lineJoin(v) { this._state.lineJoin = v; } - get miterLimit() { return this._state.miterLimit; } - set miterLimit(v) { this._state.miterLimit = v; } - get lineDashOffset() { return this._state.lineDashOffset; } - set lineDashOffset(v) { this._state.lineDashOffset = v; } - - // 变换 - save() { this._gl.save(); } - restore() { this._gl.restore(); } - translate(x, y) { this._gl.translate(x, y); } - rotate(a) { this._gl.rotate(a); } - scale(x, y) { this._gl.scale(x, y); } - transform(a, b, c, d, e, f) { this._gl.transform(a, b, c, d, e, f); } - setTransform(a, b, c, d, e, f) { this._gl.setTransform(a, b, c, d, e, f); } - resetTransform() { this._gl.resetTransform(); } - - // 矩形操作 → 直接画四边形 - fillRect(x, y, w, h) { - const c = parseColor(this._state.fillStyle); - const a = c.a * this._state.globalAlpha; - const verts = this._gl._makeQuad(x, y, w, h, 0, 0, 0, 0, c.r, c.g, c.b, a); - this._gl.addQuad(verts); - } - - strokeRect(x, y, w, h) { - const c = parseColor(this._state.strokeStyle); - const a = c.a * this._state.globalAlpha; - const lw = this._state.lineWidth; - // 4 条边 - this._fillRectRaw(x, y, w, lw, c.r, c.g, c.b, a); - this._fillRectRaw(x, y + h - lw, w, lw, c.r, c.g, c.b, a); - this._fillRectRaw(x, y, lw, h, c.r, c.g, c.b, a); - this._fillRectRaw(x + w - lw, y, lw, h, c.r, c.g, c.b, a); - } - - clearRect(x, y, w, h) { - // 用 destination-out 混合清除 - const gl = this._gl.gl; - const oldBlend = gl.getParameter(gl.BLEND_DST_RGB); - gl.blendFunc(gl.ZERO, gl.ONE_MINUS_SRC_ALPHA); - const verts = this._gl._makeQuad(x, y, w, h, 0, 0, 0, 0, 0, 0, 0, 1); - this._gl.addQuad(verts); - this._gl.flush(); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - } - - _fillRectRaw(x, y, w, h, r, g, b, a) { - const verts = this._gl._makeQuad(x, y, w, h, 0, 0, 0, 0, r, g, b, a); - this._gl.addQuad(verts); - } - - // 图像绘制 - drawImage(image, ...args) { - let sx, sy, sw, sh, dx, dy, dw, dh; - const iw = image.naturalWidth || image.width || image.videoWidth; - const ih = image.naturalHeight || image.height || image.videoHeight; - - if (args.length === 2) { - [dx, dy] = args; sx = 0; sy = 0; sw = iw; sh = ih; dw = sw; dh = sh; - } else if (args.length === 4) { - [dx, dy, dw, dh] = args; sx = 0; sy = 0; sw = iw; sh = ih; - } else { - [sx, sy, sw, sh, dx, dy, dw, dh] = args; - } - - try { - const tex = this._gl.getTexture(image); - const verts = this._gl._makeQuad( - dx, dy, dw, dh, - sx / iw, sy / ih, (sx + sw) / iw, (sy + sh) / ih, - 1, 1, 1, this._state.globalAlpha - ); - this._gl.addQuad(verts, tex); - } catch (e) { - // 纹理创建失败,静默忽略 - } - } - - // 路径操作 - beginPath() { this._currentPath = []; } - moveTo(x, y) { this._currentPath.push({ type: 'M', x, y }); } - lineTo(x, y) { this._currentPath.push({ type: 'L', x, y }); } - closePath() { this._currentPath.push({ type: 'Z' }); } - - arc(cx, cy, r, start, end, ccw = false) { - const steps = Math.max(12, Math.abs(end - start) * r | 0); - const step = (end - start) / steps * (ccw ? -1 : 1); - for (let i = 0; i <= steps; i++) { - const a = start + step * i; - const x = cx + r * Math.cos(a); - const y = cy + r * Math.sin(a); - if (i === 0) this.moveTo(x, y); - else this.lineTo(x, y); - } - } - - arcTo(x1, y1, x2, y2, r) { - // 简化实现 - const last = this._currentPath[this._currentPath.length - 1]; - if (!last) { this.moveTo(x1, y1); return; } - this.lineTo(x1, y1); - } - - ellipse(cx, cy, rx, ry, rot, start, end, ccw = false) { - const steps = Math.max(16, Math.abs(end - start) * Math.max(rx, ry) | 0); - const step = (end - start) / steps * (ccw ? -1 : 1); - for (let i = 0; i <= steps; i++) { - const a = start + step * i; - const x = cx + rx * Math.cos(a) * Math.cos(rot) - ry * Math.sin(a) * Math.sin(rot); - const y = cy + rx * Math.cos(a) * Math.sin(rot) + ry * Math.sin(a) * Math.cos(rot); - if (i === 0) this.moveTo(x, y); - else this.lineTo(x, y); - } - } - - rect(x, y, w, h) { - this.moveTo(x, y); - this.lineTo(x + w, y); - this.lineTo(x + w, y + h); - this.lineTo(x, y + h); - this.closePath(); - } - - quadraticCurveTo(cpx, cpy, x, y) { - const last = this._currentPath[this._currentPath.length - 1]; - if (!last) return; - for (let i = 1; i <= 16; i++) { - const t = i / 16, mt = 1 - t; - this.lineTo(mt * mt * last.x + 2 * mt * t * cpx + t * t * x, - mt * mt * last.y + 2 * mt * t * cpy + t * t * y); - } - } - - bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) { - const last = this._currentPath[this._currentPath.length - 1]; - if (!last) return; - for (let i = 1; i <= 16; i++) { - const t = i / 16, mt = 1 - t; - this.lineTo(mt * mt * mt * last.x + 3 * mt * mt * t * cp1x + 3 * mt * t * t * cp2x + t * t * t * x, - mt * mt * mt * last.y + 3 * mt * mt * t * cp1y + 3 * mt * t * t * cp2y + t * t * t * y); - } - } - - // 填充路径 → 三角化后用 WebGL 画 - fill() { - if (this._currentPath.length < 3) return; - const c = parseColor(this._state.fillStyle); - const a = c.a * this._state.globalAlpha; - - // 提取点坐标 - const points = this._currentPath.filter(p => p.type !== 'Z').map(p => ({ x: p.x, y: p.y })); - if (points.length < 3) return; - - // ear-clipping 三角化 - const tris = this._triangulate(points); - for (let i = 0; i < tris.length; i += 3) { - this._drawTriangle(tris[i], tris[i + 1], tris[i + 2], c.r, c.g, c.b, a); - } - } - - stroke() { - if (this._currentPath.length < 2) return; - const c = parseColor(this._state.strokeStyle); - const a = c.a * this._state.globalAlpha; - const lw = this._state.lineWidth; - - const pts = this._currentPath.filter(p => p.type !== 'Z'); - for (let i = 0; i < pts.length - 1; i++) { - this._drawThickLine(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, lw, c.r, c.g, c.b, a); - } - - // 闭合 - const last = this._currentPath[this._currentPath.length - 1]; - if (last?.type === 'Z' && pts.length > 2) { - this._drawThickLine(pts[pts.length - 1].x, pts[pts.length - 1].y, - pts[0].x, pts[0].y, lw, c.r, c.g, c.b, a); - } - } - - clip() { - // WebGL stencil 实现较复杂,先忽略 - } - - isPointInPath(x, y) { - const pts = this._currentPath.filter(p => p.type === 'M' || p.type === 'L'); - let inside = false; - for (let i = 0, j = pts.length - 1; i < pts.length; j = i++) { - const xi = pts[i].x, yi = pts[i].y, xj = pts[j].x, yj = pts[j].y; - if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside; - } - return inside; - } - - // 文字绘制 → 用离屏 Canvas 渲染后作为纹理 - fillText(text, x, y, maxWidth) { - const tc = this._textCtx; - tc.font = this._state.font; - const m = tc.measureText(text); - const fs = parseInt(this._state.font) || 10; - const w = Math.ceil(Math.min(m.width, maxWidth || Infinity)) + 4; - const h = Math.ceil(fs * 1.5) + 4; - - this._textCanvas.width = w; - this._textCanvas.height = h; - tc.font = this._state.font; - tc.textBaseline = 'top'; - tc.fillStyle = this._state.fillStyle; - tc.clearRect(0, 0, w, h); - tc.fillText(text, 2, 2, maxWidth); - - try { - const tex = this._gl.getTexture(this._textCanvas); - // 需要更新纹理 - const gl = this._gl.gl; - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._textCanvas); - - let dx = x; - if (this._state.textAlign === 'center') dx -= w / 2; - else if (this._state.textAlign === 'right' || this._state.textAlign === 'end') dx -= w; - let dy = y; - if (this._state.textBaseline === 'middle') dy -= h / 2; - else if (this._state.textBaseline === 'bottom' || this._state.textBaseline === 'alphabetic') dy -= h; - - const verts = this._gl._makeQuad(dx, dy, w, h, 0, 0, 1, 1, 1, 1, 1, this._state.globalAlpha); - this._gl.addQuad(verts, tex); - } catch (e) {} - } - - strokeText(text, x, y, maxWidth) { - const tc = this._textCtx; - tc.font = this._state.font; - const m = tc.measureText(text); - const fs = parseInt(this._state.font) || 10; - const w = Math.ceil(Math.min(m.width, maxWidth || Infinity)) + 4; - const h = Math.ceil(fs * 1.5) + 4; - - this._textCanvas.width = w; - this._textCanvas.height = h; - tc.font = this._state.font; - tc.textBaseline = 'top'; - tc.strokeStyle = this._state.strokeStyle; - tc.lineWidth = this._state.lineWidth; - tc.clearRect(0, 0, w, h); - tc.strokeText(text, 2, 2, maxWidth); - - try { - const tex = this._gl.getTexture(this._textCanvas); - const gl = this._gl.gl; - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._textCanvas); - - const verts = this._gl._makeQuad(x, y, w, h, 0, 0, 1, 1, 1, 1, 1, this._state.globalAlpha); - this._gl.addQuad(verts, tex); - } catch (e) {} - } - - measureText(text) { - this._textCtx.font = this._state.font; - return this._textCtx.measureText(text); - } - - // 像素操作 - getImageData(x, y, w, h) { - const gl = this._gl.gl; - const pixels = new Uint8Array(w * h * 4); - gl.readPixels(x, this._canvas.height - y - h, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels); - // 翻转 Y - const flipped = new Uint8Array(w * h * 4); - for (let r = 0; r < h; r++) { - flipped.set(pixels.subarray(r * w * 4, (r + 1) * w * 4), (h - r - 1) * w * 4); - } - return new ImageData(new Uint8ClampedArray(flipped.buffer), w, h); - } - - putImageData(imageData, x, y) { - const gl = this._gl.gl; - const tex = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, imageData.width, imageData.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, imageData.data); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - const verts = this._gl._makeQuad(x, y, imageData.width, imageData.height, 0, 0, 1, 1, 1, 1, 1, 1); - this._gl.addQuad(verts, tex); - } - - createImageData(w, h) { - return new ImageData(w, h); - } - - // 渐变(简化:返回一个可 addColorStop 的对象) - createLinearGradient(x0, y0, x1, y1) { - return { _type: 'linear', _stops: [], addColorStop(o, c) { this._stops.push({ o, c }); } }; - } - - createRadialGradient(x0, y0, r0, x1, y1, r1) { - return { _type: 'radial', _stops: [], addColorStop(o, c) { this._stops.push({ o, c }); } }; - } - - createPattern(image, rep) { - return { _type: 'pattern', image, rep }; - } - - // getLineDash / setLineDash - getLineDash() { return []; } - setLineDash() {} - - // 内部:三角形绘制 - _drawTriangle(p0, p1, p2, r, g, b, a) { - const m = this._gl.matrix; - const pr = this._gl.projection; - const transform = (x, y) => { - const wx = m[0] * x + m[3] * y + m[6]; - const wy = m[1] * x + m[4] * y + m[7]; - return [pr[0] * wx + pr[6], pr[4] * wy + pr[7]]; - }; - const [x0, y0] = transform(p0.x, p0.y); - const [x1, y1] = transform(p1.x, p1.y); - const [x2, y2] = transform(p2.x, p2.y); - - const verts = new Float32Array([ - x0, y0, 0, 0, r, g, b, a, 0, - x1, y1, 0, 0, r, g, b, a, 0, - x2, y2, 0, 0, r, g, b, a, 0, - x0, y0, 0, 0, r, g, b, a, 0, - x2, y2, 0, 0, r, g, b, a, 0, - x1, y1, 0, 0, r, g, b, a, 0, - ]); - this._gl.flush(); // 先刷新当前批次 - const gl = this._gl.gl; - gl.bindBuffer(gl.ARRAY_BUFFER, this._gl.vbo); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, verts); - gl.uniformMatrix3fv(this._gl.loc.u_proj, false, this._gl.projection); - gl.uniform1i(this._gl.loc.u_useTex, false); - gl.vertexAttribPointer(this._gl.loc.a_pos, 2, gl.FLOAT, false, 36, 0); - gl.vertexAttribPointer(this._gl.loc.a_uv, 2, gl.FLOAT, false, 36, 8); - gl.vertexAttribPointer(this._gl.loc.a_color, 4, gl.FLOAT, false, 36, 16); - gl.drawArrays(gl.TRIANGLES, 0, 6); - this._gl.drawCalls++; - } - - // 内部:粗线绘制 - _drawThickLine(x0, y0, x1, y1, w, r, g, b, a) { - const dx = x1 - x0, dy = y1 - y0; - const len = Math.sqrt(dx * dx + dy * dy); - if (len === 0) return; - const nx = -dy / len * w / 2, ny = dx / len * w / 2; - const verts = this._gl._makeQuad( - Math.min(x0 + nx, x1 + nx, x0 - nx, x1 - nx), - Math.min(y0 + ny, y1 + ny, y0 - ny, y1 - ny), - Math.abs(dx) + w, Math.abs(dy) + w, - 0, 0, 0, 0, r, g, b, a - ); - this._gl.addQuad(verts); - } - - // 内部:ear-clipping 三角化 - _triangulate(points) { - if (points.length < 3) return []; - const result = []; - const verts = [...points]; - if (this._polyArea(verts) > 0) verts.reverse(); - - let n = verts.length, i = 0; - while (n > 2 && i < 1000) { - i++; - const prev = (i - 1 + n) % n, next = (i + 1) % n; - if (this._isEar(prev, i % n, next, verts, n)) { - result.push(verts[prev], verts[i % n], verts[next]); - verts.splice(i % n, 1); - n--; - i = 0; - } - } - return result; - } - - _polyArea(p) { - let a = 0; - for (let i = 0; i < p.length; i++) { - const j = (i + 1) % p.length; - a += p[i].x * p[j].y - p[j].x * p[i].y; - } - return a / 2; - } - - _isEar(prev, cur, next, v, n) { - const a = v[prev], b = v[cur % n], c = v[next]; - if ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) <= 0) return false; - for (let i = 0; i < n; i++) { - if (i === prev || i === cur % n || i === next) continue; - if (this._inTri(v[i], a, b, c)) return false; - } - return true; - } - - _inTri(p, a, b, c) { - const d1 = (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y); - const d2 = (p.x - c.x) * (b.y - c.y) - (b.x - c.x) * (p.y - c.y); - const d3 = (p.x - a.x) * (c.y - a.y) - (c.x - a.x) * (p.y - a.y); - const hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0); - const hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0); - return !(hasNeg && hasPos); - } - } - - // ================================================================ - // 注入:拦截 getContext('2d'),返回转译代理 - // ================================================================ - const origGetContext = HTMLCanvasElement.prototype.getContext; - const rendererMap = new WeakMap(); // canvas → GLRenderer - const proxyMap = new WeakMap(); // canvas → Canvas2DProxy - - HTMLCanvasElement.prototype.getContext = function (type, ...args) { - // 2d 请求 → 转译为 WebGL - if (type === '2d' || type === '2D') { - if (proxyMap.has(this)) return proxyMap.get(this); - - try { - let renderer = rendererMap.get(this); - if (!renderer) { - renderer = new GLRenderer(this); - rendererMap.set(this, renderer); - } - const proxy = new Canvas2DProxy(this, renderer); - proxyMap.set(this, proxy); - log(`✅ Canvas 2D → WebGL 转译成功 (${this.width}x${this.height})`); - return proxy; - } catch (e) { - // WebGL 不可用,降级到原生 Canvas 2D - log('⚠️ WebGL 不可用,降级到 Canvas 2D:', e.message); - return origGetContext.call(this, type, ...args); - } - } - - // WebGL 请求 → 优化参数 - if (type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl') { - const opts = args[0] || {}; - Object.assign(opts, { - antialias: false, - powerPreference: 'high-performance', - desynchronized: true, - failIfMajorPerformanceCaveat: false - }); - args[0] = opts; - } - - return origGetContext.call(this, type, ...args); - }; - - // ================================================================ - // 帧循环:自动 beginFrame / endFrame - // ================================================================ - const origRAF = window.requestAnimationFrame; - let frameScheduled = false; - - function scheduleFlush() { - if (frameScheduled) return; - frameScheduled = true; - origRAF.call(window, () => { - // 刷新所有活跃的渲染器 - for (const [, renderer] of rendererMap) { - renderer.endFrame(); - renderer.beginFrame(); - } - frameScheduled = false; - }); - } - - // 拦截 requestAnimationFrame 以自动刷新 - window.requestAnimationFrame = function (cb) { - return origRAF.call(window, function (...a) { - scheduleFlush(); - cb(...a); - }); - }; - - // ================================================================ - // CSS 优化 - // ================================================================ - const style = document.createElement('style'); - style.textContent = 'canvas{will-change:transform!important}'; - (document.head || document.documentElement).prepend(style); - - // ================================================================ - // 完成 - // ================================================================ - log('🚀 Canvas → WebGL 转译器已注入'); - log(' 所有 Canvas 2D 操作将自动转译为 WebGL GPU 渲染'); - -})(); diff --git a/userscript/codemao-heart-jit.user.js b/userscript/codemao-heart-jit.user.js deleted file mode 100644 index 70484f0..0000000 --- a/userscript/codemao-heart-jit.user.js +++ /dev/null @@ -1,806 +0,0 @@ -// ==UserScript== -// @name 编程猫 Heart JIT 加速器 -// @namespace https://gitee.com/shuiyu1123/codemao-webgl-engine -// @version 3.1.0 -// @description 绕过 Heart 运行时解释器,将积木 AST 直接编译为原生 JS 执行 -// @author codemao-webgl-engine -// @match https://player.codemao.cn/* -// @match https://shequ.codemao.cn/* -// @match https://kitten.codemao.cn/* -// @match https://ide.codemao.cn/* -// @run-at document-start -// @grant none -// @license MIT -// ==/UserScript== - -/** - * ============================================================ - * 编程猫 Heart JIT 加速器 v3.1 - * ============================================================ - * - * 从 player_v2.js 源码分析得到的注入点: - * - * 1. PIXI.Application 创建时挂在 canvas.__pixi_app - * 2. Redux store 通过 T.a.get_store() 访问 - * 3. Heart 运行时通过 Object(E.b)().get_runtime_manager() 获取 - * 4. 游戏循环在 requestAnimationFrame 中运行 - * 5. BCM 数据加载后通过 LOAD_BCM action 触发 - * - * 注入策略: - * - 拦截 PIXI.Application 构造函数 → 拿到 pixi app 实例 - * - 通过 pixi app 的 ticker 回调找到 Heart 运行时 - * - 替换 Interpreter 的 step 方法为 JIT 编译版本 - * - * ============================================================ - */ - -(function () { - 'use strict'; - - const TAG = '🐱[Heart-JIT]'; - const log = (...a) => console.log(TAG, ...a); - const warn = (...a) => console.warn(TAG, ...a); - - // ================================================================ - // 配置 - // ================================================================ - const CONFIG = { - enabled: true, - debug: false, - fallbackOnError: true, - profileInterval: 5000, // 性能采样间隔 - }; - - // ================================================================ - // 统计 - // ================================================================ - const stats = { - jitted: 0, - interpreted: 0, - fallbacks: 0, - executions: 0, - compileTime: 0, - avgStepTime: 0, - pixiApps: 0, - heartFound: false, - }; - - // ================================================================ - // JIT 编译器 - // ================================================================ - class JITCompiler { - constructor() { - this.cache = new Map(); - } - - /** - * 将一个积木过程编译为原生 JS 生成器函数 - * 使用生成器是为了支持 Heart 的 yield 协程机制 - */ - compile(blockChain) { - if (!blockChain) return null; - const id = blockChain.id || 'chain_' + Math.random(); - if (this.cache.has(id)) return this.cache.get(id); - - const t0 = performance.now(); - try { - const body = this.emitChain(blockChain, 0); - // 生成器函数,支持 yield 协程 - const fn = new Function('ctx', ` - "use strict"; - const { runtime, data, eventBus, ohno, entity_id, interpreter_id } = ctx; - let yield_count = 0; - let _break_flag = false; - ${body} - `); - stats.compileTime += performance.now() - t0; - stats.jitted++; - this.cache.set(id, fn); - if (CONFIG.debug) log(`JIT: compiled ${blockChain.type || 'chain'} (${id}) in ${(performance.now()-t0).toFixed(2)}ms`); - return fn; - } catch (e) { - stats.fallbacks++; - if (CONFIG.debug) warn('JIT compile failed:', e); - return null; - } - } - - emitChain(block, depth) { - if (!block || depth > 50) return ''; - let code = this.emitBlock(block, depth); - if (block.next_block) { - code += '\n' + this.emitChain(block.next_block, depth); - } - return code; - } - - emitBlock(block, depth) { - const t = block.type; - const p = block.params || {}; - const children = block.child_block || []; - - switch (t) { - // === 变量 === - case 'variables_set': - return `runtime.set_variable(${JSON.stringify(p.VAR)}, ${this.val(p.VALUE, depth)}, entity_id, interpreter_id);`; - case 'variables_get': - return `runtime.get_variable(${JSON.stringify(p.VAR)}, entity_id, interpreter_id)`; - case 'change_variable': { - const m = p.method || 'increase'; - return `(function(){var c=runtime.get_variable(${JSON.stringify(p.valname)},entity_id,interpreter_id);var a=${this.val(p.n,depth)};runtime.set_variable(${JSON.stringify(p.valname)},${m==='increase'?'c+a':'c-a'},entity_id,interpreter_id);})()`; - } - - // === 逻辑 === - case 'logic_compare': { - const op = {EQ:'==',NEQ:'!=',LT:'<',LTE:'<=',GT:'>',GTE:'>='}[p.OP] || '=='; - return `(${this.val(p.A,depth)} ${op} ${this.val(p.B,depth)})`; - } - case 'logic_operation': - return p.OP === 'AND' - ? `(${this.val(p.A,depth)} && ${this.val(p.B,depth)})` - : `(${this.val(p.A,depth)} || ${this.val(p.B,depth)})`; - case 'logic_negate': - return `(!${this.val(p.BOOL,depth)})`; - case 'logic_boolean': - return p.BOOL === 'TRUE' ? 'true' : 'false'; - - // === 数学 === - case 'math_arithmetic': { - const ops = {ADD:'+',MINUS:'-',MULTIPLY:'*',DIVIDE:'/'}[p.OP]; - if (ops) return `(${this.val(p.A,depth)} ${ops} ${this.val(p.B,depth)})`; - if (p.OP === 'POWER') return `Math.pow(${this.val(p.A,depth)},${this.val(p.B,depth)})`; - return `(${this.val(p.A,depth)} + ${this.val(p.B,depth)})`; - } - case 'math_modulo': - return `(${this.val(p.DIVIDEND,depth)} % ${this.val(p.DIVISOR,depth)})`; - case 'random': - return `Math.floor(${this.val(p.a,depth)} + Math.random() * (${this.val(p.b,depth)} - ${this.val(p.a,depth)} + 1))`; - - // === 文本 === - case 'text_join': { - const parts = []; - for (let i = 0; p['ADD'+i] !== undefined; i++) parts.push(this.val(p['ADD'+i], depth)); - return parts.length ? `("" + ${parts.join(' + ')})` : '""'; - } - case 'text_length': - return `String(${this.val(p.VALUE,depth)}).length`; - case 'text_contain': - return `(String(${this.val(p.TEXT1,depth)}).indexOf(String(${this.val(p.TEXT2,depth)})) !== -1)`; - case 'text_implicit': - case 'default_value': - case 'multiline_text': - return JSON.stringify(p.TEXT || p.VALUE || ''); - - // === 控制流 === - case 'controls_if': - case 'controls_if_no_else': - case 'controls_if_dropdown': { - const conds = p.conditions || p.if_dropdown_conditions || []; - let code = ''; - for (let i = 0; i < conds.length; i++) { - const cond = this.val(conds[i], depth); - const body = children[i] ? this.emitChain(children[i], depth+1) : ''; - code += (i === 0 ? 'if' : ' else if') + `(${cond}){${body}}`; - } - if (children.length > conds.length) { - code += ` else {${this.emitChain(children[children.length-1], depth+1)}}`; - } - return code; - } - case 'repeat_n_times': { - const times = this.val(p.TIMES, depth); - const body = children[0] ? this.emitChain(children[0], depth+1) : ''; - return `for(var _i=0;_i<${times};_i++){${body}if(_break_flag){_break_flag=false;break;}}`; - } - case 'repeat_forever': { - const body = children[0] ? this.emitChain(children[0], depth+1) : ''; - return `while(true){${body}yield_count++;if(yield_count>=500){yield_count=0;}if(_break_flag){_break_flag=false;break;}}`; - } - case 'repeat_forever_until': { - const cond = this.val(p.condition, depth); - const body = children[0] ? this.emitChain(children[0], depth+1) : ''; - return `while(!(${cond})){${body}yield_count++;if(yield_count>=500){yield_count=0;}if(_break_flag){_break_flag=false;break;}}`; - } - case 'wait': - return `/* wait ${this.val(p.time,depth)}s */`; - case 'break': - return '_break_flag=true;'; - - // === 过程 === - case 'procedures_callnoreturn': - case 'procedures_2_callnoreturn': - case 'procedures_callreturn': - case 'procedures_2_callreturn': { - const name = p.NAME || block.procedure_name || ''; - const args = Object.entries(p).filter(([k]) => k !== 'NAME').map(([k,v]) => `${k}:${this.val(v,depth)}`).join(','); - return `runtime.call_procedure(${JSON.stringify(name)},{${args}},entity_id,interpreter_id)`; - } - case 'procedures_return_value': - case 'procedures_2_return_value': - return `/* return ${this.val(p.VALUE,depth)} */`; - - // === 广播 === - case 'self_broadcast': - return `runtime.broadcaster_sending_message(entity_id,${JSON.stringify(p.message)},false);`; - case 'self_broadcast_and_wait': - return `runtime.broadcaster_sending_message(entity_id,${JSON.stringify(p.message)},true);`; - - // === 时间 === - case 'get_time': { - const m = {year:'getFullYear',month:'getMonth',date:'getDate',week:'getDay',hour:'getHours',minute:'getMinutes',second:'getSeconds'}[p.op]; - return m ? `(new Date).${m}()${p.op==='month'?'+1':''}` : '0'; - } - case 'get_cur_frames': - return 'runtime.get_elapsed_frames()'; - case 'get_timer': - return 'runtime.get_timer_elapsed_s()'; - case 'reset_timer': - return 'runtime.reset_timer();'; - case 'wait_seconds': { - const secs = this.val(p.time, depth); - return `/* wait ${secs}s */`; - } - - // === 列表 === - case 'lists_append': - return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);if(Array.isArray(l))l.push(${this.val(p.VALUE,depth)});})()`; - case 'lists_get_value': - return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);if(!Array.isArray(l))return undefined;var i=${this.val(p.INDEX,depth)};${p.TYPE==='last'?'i=l.length;':''}i=i>0?i-1:i+l.length;return l[i];})()`; - case 'lists_length': - return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);return Array.isArray(l)?l.length:0;})()`; - case 'lists_delete': - return `(function(){var l=runtime.get_variable(${JSON.stringify(p.VAR)},entity_id,interpreter_id);if(!Array.isArray(l))return;var i=${this.val(p.INDEX,depth)};${p.TYPE==='last'?'l.pop();':'i=i>0?i-1:i+l.length;l.splice(i,1);'}})()`; - - // === 通用 domain function === - default: - return this.emitDomainFunc(block, depth); - } - } - - emitDomainFunc(block, depth) { - const p = block.params || {}; - const args = Object.entries(p).map(([k,v]) => `${k}:${this.val(v,depth)}`).join(','); - return `(function(){try{var df=runtime.get_domain_function(${JSON.stringify(block.type)});if(df)df({${args}},entity_id,interpreter_id,ctx);}catch(e){if(${CONFIG.debug})console.error(${JSON.stringify(block.type)},e);}})()`; - } - - val(v, depth) { - if (v === undefined || v === null) return 'undefined'; - if (typeof v === 'number' || typeof v === 'boolean') return JSON.stringify(v); - if (typeof v === 'string') return JSON.stringify(v); - if (typeof v === 'object' && v.type) { - const inner = this.emitBlock(v, depth + 1); - if (inner.length < 100 && !inner.includes(';')) return inner; - return `(function(){${inner}})()`; - } - return JSON.stringify(v); - } - } - - // ================================================================ - // 注入器 — 通过拦截 PIXI 和 RAF 找到 Heart 运行时 - // ================================================================ - class HeartJITInjector { - constructor() { - this.jit = new JITCompiler(); - this.heartRM = null; // runtime_manager - this.injected = false; - this.patchedInterpreters = new WeakSet(); - this.origRAF = window.requestAnimationFrame; - this.frameCount = 0; - } - - /** - * 安装拦截器 - */ - install() { - this.interceptRAF(); - this.interceptPIXI(); - this.startProfiling(); - log('拦截器已安装,等待作品加载...'); - } - - /** - * 拦截 requestAnimationFrame 以检测游戏循环 - */ - interceptRAF() { - const self = this; - const orig = this.origRAF; - - window.requestAnimationFrame = function(cb) { - return orig.call(window, function(time) { - self.frameCount++; - // 每 120 帧尝试一次注入 - if (self.frameCount % 120 === 0 && !self.injected) { - self.tryInject(); - } - cb(time); - }); - }; - log('RAF 拦截已安装'); - } - - /** - * 拦截 PIXI.Application 构造函数 - */ - interceptPIXI() { - const self = this; - // 等 PIXI 加载 - const check = setInterval(() => { - if (window.PIXI && window.PIXI.Application) { - clearInterval(check); - self.hookPIXIApplication(); - } - }, 500); - } - - hookPIXIApplication() { - const self = this; - const OrigApp = window.PIXI.Application; - - // 保存原始构造函数 - const PatchedApp = function(...args) { - const app = new OrigApp(...args); - stats.pixiApps++; - log(`PIXI.Application 创建 (#${stats.pixiApps})`); - - // 标记 canvas - if (app.view) { - app.view.__pixi_app = app; - app.view.__heart_jit_marked = true; - } - - // 尝试通过 ticker 找到 Heart - self.tryInjectViaPIXI(app); - - return app; - }; - - // 复制原型 - PatchedApp.prototype = OrigApp.prototype; - PatchedApp.prototype.constructor = PatchedApp; - - // 替换 - window.PIXI.Application = PatchedApp; - log('PIXI.Application 已 hook'); - } - - /** - * 通过 PIXI app 尝试注入 - */ - tryInjectViaPIXI(app) { - // PIXI ticker 会调用 render,而 render 连接到 Heart 的更新循环 - // 我们在 ticker 回调中查找 Heart 实例 - const self = this; - let checkCount = 0; - - const checkTicker = () => { - checkCount++; - if (self.injected || checkCount > 600) return; // 10 秒超时 - - // 尝试从 React fiber 树中找到 Heart 实例 - const heart = self.findHeartFromDOM(); - if (heart) { - self.setupJIT(heart); - return; - } - - // 继续检查 - requestAnimationFrame(checkTicker); - }; - - requestAnimationFrame(checkTicker); - } - - /** - * 从 DOM 中查找 Heart 运行时实例 - * 通过 React fiber 树遍历 - */ - findHeartFromDOM() { - try { - // 查找 canvas 元素 - const canvas = document.querySelector('canvas'); - if (!canvas) return null; - - // React 16+ fiber 节点 - const fiberKey = Object.keys(canvas).find(k => - k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$') - ); - if (!fiberKey) return null; - - let fiber = canvas[fiberKey]; - // 向上遍历 fiber 树查找包含 heart 的组件 - for (let i = 0; i < 50 && fiber; i++) { - const state = fiber.memoizedState; - const props = fiber.memoizedProps; - - // 检查 stateNode - if (fiber.stateNode && fiber.stateNode.get_heart) { - const heart = fiber.stateNode.get_heart(); - if (heart && heart.get_runtime_manager) { - return heart; - } - } - - // 检查 props 中的 heart 引用 - if (props && props.get_heart) { - const heart = props.get_heart(); - if (heart && heart.get_runtime_manager) { - return heart; - } - } - - // 检查 ref - if (fiber.ref && fiber.ref.current && fiber.ref.current.get_heart) { - const heart = fiber.ref.current.get_heart(); - if (heart && heart.get_runtime_manager) { - return heart; - } - } - - fiber = fiber.return; - } - - // 备用:查找全局变量 - if (window.__heart) return window.__heart; - if (window.__pixi_app) { - const app = window.__pixi_app; - if (app.__heart) return app.__heart; - } - - } catch (e) { - if (CONFIG.debug) warn('DOM 查找失败:', e); - } - return null; - } - - /** - * 主注入尝试 - */ - tryInject() { - if (this.injected) return true; - - const heart = this.findHeartFromDOM(); - if (heart) { - this.setupJIT(heart); - return true; - } - - // 备用方案:从 Redux store 查找 - const store = this.findReduxStore(); - if (store) { - const state = store.getState(); - // Heart 运行时可能在某个 reducer 的 state 中 - if (state.cloud_states && state.cloud_states.loading_state === 'load_done') { - log('检测到作品已加载,尝试从 store 获取 Heart...'); - this.tryInjectFromStore(store); - } - } - - return false; - } - - /** - * 查找 Redux store - */ - findReduxStore() { - // 编程猫用 T.a.get_store() 暴露 store - // 尝试从 window 上查找 - try { - // 查找 store 的特征:dispatch + getState + subscribe - if (window.__store) return window.__store; - - // 扫描 window 上的对象 - for (const key of Object.keys(window)) { - const obj = window[key]; - if (obj && typeof obj.dispatch === 'function' && - typeof obj.getState === 'function' && - typeof obj.subscribe === 'function') { - return obj; - } - } - } catch (e) {} - return null; - } - - /** - * 从 Redux store 尝试注入 - */ - tryInjectFromStore(store) { - // 监听 store 变化,当 Heart 初始化完成后注入 - const self = this; - let lastState = store.getState(); - - store.subscribe(() => { - const state = store.getState(); - if (state.run_states && state.run_states.run_state === 1 && !self.injected) { - // 游戏正在运行 - self.tryInject(); - } - lastState = state; - }); - } - - /** - * 设置 JIT 加速 - */ - setupJIT(heart) { - if (this.injected) return; - - try { - const rm = heart.get_runtime_manager(); - if (!rm) { - warn('Runtime manager 未找到'); - return; - } - - this.heartRM = rm; - this.injected = true; - stats.heartFound = true; - - // 拦截 Interpreter 创建 - this.patchInterpreterFactory(rm); - - log('✅ Heart JIT 加速器注入成功!'); - log(' Heart 运行时已找到,JIT 编译器就绪'); - - // 通知用户 - this.showNotification('Heart JIT 加速器已激活 ⚡'); - - } catch (e) { - warn('注入失败:', e); - stats.fallbacks++; - } - } - - /** - * Patch Interpreter 工厂方法 - * 当新的 Interpreter 实例创建时,自动替换其 step 方法 - */ - patchInterpreterFactory(rm) { - const self = this; - const jit = this.jit; - - // 拦截 load 方法(当积木加载时触发) - const origLoad = rm.load?.bind(rm); - if (origLoad) { - rm.load = function(entities) { - const result = origLoad(entities); - // 加载后 patch 所有 Interpreter - self.patchAllInterpreters(rm); - return result; - }; - } - - // 拦截 run 方法 - const origRun = rm.run?.bind(rm); - if (origRun) { - rm.run = function() { - const result = origRun(); - self.patchAllInterpreters(rm); - return result; - }; - } - - // 立即 patch 已有的 interpreter - this.patchAllInterpreters(rm); - } - - /** - * Patch 所有活跃的 Interpreter 实例 - */ - patchAllInterpreters(rm) { - // Heart 的 interpreter 通常存储在内部数组中 - // 通过遍历 rm 的属性查找 - try { - for (const key of Object.keys(rm)) { - const val = rm[key]; - if (Array.isArray(val)) { - for (const item of val) { - if (item && typeof item.step === 'function' && !this.patchedInterpreters.has(item)) { - this.patchInterpreter(item); - } - } - } - if (val && typeof val === 'object' && !Array.isArray(val)) { - for (const subKey of Object.keys(val || {})) { - const sub = val[subKey]; - if (sub && typeof sub.step === 'function' && !this.patchedInterpreters.has(sub)) { - this.patchInterpreter(sub); - } - } - } - } - } catch (e) { - if (CONFIG.debug) warn('patch interpreters 失败:', e); - } - } - - /** - * Patch 单个 Interpreter 实例 - */ - patchInterpreter(interpreter) { - if (this.patchedInterpreters.has(interpreter)) return; - this.patchedInterpreters.add(interpreter); - - const jit = this.jit; - const origStep = interpreter.step?.bind(interpreter); - - if (!origStep) return; - - // 获取根积木 - const rootBlock = interpreter.root_block; - if (!rootBlock) return; - - // JIT 编译 - const jittedFn = jit.compile(rootBlock); - if (!jittedFn) return; - - // 替换 step 方法 - interpreter.step = function() { - stats.executions++; - try { - // 尝试 JIT 执行 - const ctx = { - runtime: this.run_mgr || this.runtime_manager || {}, - data: this.program_state || {}, - eventBus: {}, - ohno: {}, - entity_id: this.original_entities?.target_entity || '', - interpreter_id: this.original_entities?.interpreter_id || '', - }; - jittedFn(ctx); - return { done: true }; - } catch (e) { - // 降级到原始解释器 - stats.fallbacks++; - return origStep(); - } - }; - - stats.jitted++; - if (CONFIG.debug) log(`Patched interpreter: ${rootBlock.type || 'unknown'}`); - } - - /** - * 显示通知 - */ - showNotification(msg) { - try { - const div = document.createElement('div'); - div.style.cssText = ` - position:fixed;top:20px;left:50%;transform:translateX(-50%); - background:linear-gradient(135deg,#0f0,#0a0);color:#fff; - padding:12px 24px;border-radius:8px;font-family:monospace;font-size:14px; - z-index:999999;box-shadow:0 4px 20px rgba(0,255,0,0.3); - animation:jit-fade 3s forwards; - `; - div.textContent = msg; - document.body.appendChild(div); - - // 添加动画 - const style = document.createElement('style'); - style.textContent = ` - @keyframes jit-fade { - 0% { opacity: 0; transform: translateX(-50%) translateY(-20px); } - 20% { opacity: 1; transform: translateX(-50%) translateY(0); } - 80% { opacity: 1; } - 100% { opacity: 0; transform: translateX(-50%) translateY(-10px); } - } - `; - document.head.appendChild(style); - - setTimeout(() => div.remove(), 3000); - } catch (e) {} - } - - /** - * 性能监控 - */ - startProfiling() { - setInterval(() => { - if (this.injected && CONFIG.debug) { - log(`Stats: jitted=${stats.jitted}, exec=${stats.executions}, fallback=${stats.fallbacks}, compile=${stats.compileTime.toFixed(1)}ms`); - } - }, CONFIG.profileInterval); - } - } - - // ================================================================ - // 性能面板 - // ================================================================ - class PerfPanel { - constructor() { - this.el = null; - this.interval = null; - } - - show() { - if (this.el) return; - const panel = document.createElement('div'); - panel.id = 'heart-jit-panel'; - panel.style.cssText = ` - position:fixed;top:10px;right:10px;z-index:999999; - background:rgba(0,0,0,0.9);color:#0f0;font-family:'Courier New',monospace; - font-size:11px;padding:10px 14px;border-radius:6px;border:1px solid #0f0; - min-width:200px;box-shadow:0 0 15px rgba(0,255,0,0.2); - cursor:move;user-select:none; - `; - - const title = document.createElement('div'); - title.style.cssText = 'font-size:13px;font-weight:bold;margin-bottom:6px;color:#0f0;border-bottom:1px solid #030;padding-bottom:4px;'; - title.textContent = '🐱 Heart JIT v3.1'; - panel.appendChild(title); - - const content = document.createElement('div'); - content.id = 'jit-stats'; - panel.appendChild(content); - - // 拖拽 - let dragging = false, ox, oy; - panel.addEventListener('mousedown', e => { dragging=true; ox=e.clientX-panel.offsetLeft; oy=e.clientY-panel.offsetTop; }); - document.addEventListener('mousemove', e => { if(dragging){panel.style.left=(e.clientX-ox)+'px';panel.style.top=(e.clientY-oy)+'px';panel.style.right='auto';} }); - document.addEventListener('mouseup', () => dragging=false); - - // 右键切换 debug - panel.addEventListener('contextmenu', e => { e.preventDefault(); CONFIG.debug=!CONFIG.debug; }); - - document.body.appendChild(panel); - this.el = content; - - this.interval = setInterval(() => this.update(), 500); - } - - update() { - if (!this.el) return; - const s = stats; - const status = s.heartFound ? '✅ 已注入' : '⏳ 等待...'; - this.el.innerHTML = ` -