从 1.8MB webpack bundle 中提取的关键信息: - Heart 运行时完整类结构 (Registry, Compiler, Interpreter, RuntimeManager) - 80+ domain functions (积木实现) 清单 - Interpreter.do_step() 执行流程详解 - BCM 加载流程 - PIXI.js 渲染层架构 - 性能瓶颈分析和 JIT 切入点 - 关键代码的 webpack 模块位置
443 lines
15 KiB
Markdown
443 lines
15 KiB
Markdown
# 编程猫 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 加载 |
|