上个月我给一个互动影像项目写实时画面生成,TouchDesigner 在前端负责交互逻辑和渲染输出,ComfyUI 在后台跑 SD 工作流。当时最崩溃的一幕是,演出前两小时,TouchDesigner 这边的请求已经发过去了,ComfyUI 那边日志却一动不动。排查到最后,问题居然只是提交的 JSON 里少了 client_id,导致 ComfyUI 虽然接收了任务,却没法把执行状态回推给 TouchDesigner。
这种问题在 TouchDesigner 对接 ComfyUI 时太典型了。两个工具本身都能独立跑得很稳,但一旦牵扯到 HTTP 请求、WebSocket、工作流 JSON 格式、base64 图片解码,各种小坑就会排着队冒出来。这篇文章我把实际项目中踩过的、以及社区里高频出现的对接问题整理了一遍,从通信架构讲起,到环境配置、工作流提交、图片回传、性能调优,最后给一份可以直接对着排查的报错速查表。无论你是刚开始在 TouchDesigner 里调 ComfyUI,还是已经跑通但总觉得不稳,这篇应该都能帮上忙。
1. 对接前先把架构理清:API、WebSocket 和 TouchDesigner 的通信链路
很多人在 TouchDesigner 里连不上 ComfyUI,第一反应是代码写错了,但其实是没搞明白两边的通信链路。TouchDesigner 和 ComfyUI 不是像插件那样"装进去就能用"的关系,而是两个独立的进程通过 HTTP 和 WebSocket 互相喊话。这个结构理清了,后面所有问题都会好定位得多。
1.1 两种通信通道的分工
ComfyUI 启动后默认监听 8188 端口,这个端口上同时跑着两条通道:
一类是 HTTP API,主要干三件事:提交工作流、查询任务历史、读取生成好的图片。提交工作流走 POST /prompt,把 API 格式的 JSON 塞进去,ComfyUI 会返回一个 prompt_id,之后所有和这个任务有关的操作都拿这个 ID 说话。
另一类是 WebSocket,地址是 ws://127.0.0.1:8188/ws?clientId=xxx。它负责把执行状态实时推给客户端:任务开始执行了、某个节点跑完了、全部执行完毕,都会推消息过来。TouchDesigner 如果不走 WebSocket,就只能在提交任务后傻等,然后反复去查 /history,体验很差,而且容易漏状态。
这两条通道的分工可以这么理解:HTTP 是你去食堂窗口打饭,喊一声"来一份宫保鸡丁",师傅给你一张号牌;WebSocket 是后厨的广播,叫到你的号你就去取餐。没有广播,你只能隔一会儿就去窗口问一次"好了没",费劲还容易错过。
1.2 工作流的两副面孔:UI 工作流与 API 工作流
这是 TouchDesigner 对接 ComfyUI 时最容易翻车的地方。ComfyUI 里的工作流有两种完全不同的 JSON 格式,很多人把网页里 Save 导出的 JSON 直接丢给 TouchDesigner 的 HTTP 请求,结果 ComfyUI 返回 400,一脸懵。
- UI 工作流(Workflow JSON):在网页画布上点
Save导出的格式,里面记录了节点在图上的位置、连线走向、分组信息,甚至还有折叠状态。这种格式是给人看的,是为了让你重新打开画布还能恢复排版。API 不认这个格式。 - API 工作流(API Format JSON):在 ComfyUI 界面里点
Save (API Format)导出的格式。结构是一个 JSON 对象,key 是节点 ID(字符串),value 里有class_type(节点类型)和inputs(输入参数)。这个才是POST /prompt需要的格式。
判断一份 JSON 是不是 API 格式,有个很粗暴的办法:看它最外层是不是"以节点 ID 为 key 的字典",并且没有 nodes、links 这种 UI 字段。API 格式长这样:
json复制{
"3": {
"class_type": "KSampler",
"inputs": {
"seed": 42,
"steps": 20,
"cfg": 7,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": ["4", 0],
"positive": ["6", 0],
"negative": ["7", 0],
"latent_image": ["5", 0]
}
}
}
UI 格式则会有 "nodes": [...]、"links": [...] 这种结构。TouchDesigner 端提交之前,务必先确认你手里的是 API 格式。
1.3 最小的联通测试
在 TouchDesigner 里写任何复杂逻辑之前,先做一个最小连通性测试。打开一个 Script DAT,切到 Python 模式,直接跑:
python复制import requests
resp = requests.get("http://127.0.0.1:8188/system_stats", timeout=5)
print(resp.status_code)
print(resp.json())
如果输出 200 和系统信息,说明 TouchDesigner 能摸到 ComfyUI 的 HTTP 接口。如果这里就报错,先别急着调工作流,优先排查 ComfyUI 是否启动、端口是否被占用、地址是不是写错了。
这里有个细节:地址尽量写 127.0.0.1,不要写 localhost。有些环境里 localhost 会被解析成 IPv6 的 ::1,而 ComfyUI 默认只监听了 IPv4 的 127.0.0.1,结果就是浏览器能开、TouchDesigner 连不上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境与版本匹配:为什么连不上、装了却不识别
连通性测试过了,不代表后面就一帆风顺。环境层面的问题最隐蔽,因为它不是代码逻辑错误,而是两边的运行环境压根没对齐。
2.1 端口绑定与访问地址的坑
ComfyUI 默认启动只绑定本机回环地址,也就是说只有你这台电脑自己能访问。如果你把 TouchDesigner 跑在另一台机器上,远程去调 ComfyUI,必须让 ComfyUI 监听所有网卡。启动参数是加 --listen 0.0.0.0。
还有个更隐蔽的坑:有些整合包或自定义启动脚本会默认带上 --listen 127.0.0.1,写死了本机回环。你远程访问时提示拒绝连接,其实不是代码问题,是启动参数没改。手动改启动脚本里的 --listen 为 0.0.0.0 再重启,顺便确认防火墙放行了 8188 端口。
2.2 版本差异引发的 API 行为变化
ComfyUI 更新非常频繁,API 的字段虽然大体稳定,但偶尔会有调整。比如某些旧版本里 /prompt 返回的字段是 prompt_id,新版本可能还带上了 number 这类额外字段;某些节点在版本升级后改名、参数缺省值变化,旧工作流拿到新版本里跑,会报 ValueError: unknown type 之类的错误。
遇到这种情况,先在浏览器里打开 ComfyUI 网页,把旧工作流重新加载一遍,看一下有没有红色报错节点。如果网页端就报节点类型错误,说明工作流本身需要更新,跟 TouchDesigner 没关系。
2.3 模型目录与额外模型路径
TouchDesigner 提交工作流时,inputs 里的 ckpt_name、lora_name 等参数必须是字符串,而且这个字符串要能匹配 ComfyUI 端已有的模型文件。常见的错误场景:
- 模型文件放在
ComfyUI/models/checkpoints/下,工作流里写了带路径前缀的名字,比如foo/bar.safetensors,但实际文件在根目录,匹配失败。 - 用整合包的用户,模型可能被放在自定义的
extra_model_paths.yaml指定的目录里。ComfyUI 虽然能加载,但 API 返回的模型名称可能是相对路径,你从网页端复制过来的值可能和 TouchDesigner 里填的对不上。
最稳妥的办法是:在 ComfyUI 网页端手动跑通一次工作流,然后从浏览器的 Network 面板里复制实际提交给 /prompt 的 JSON,那里面 model 名称就是 ComfyUI 现在真正识别的名字,直接拿它去 TouchDesigner 用。
2.4 TouchDesigner 内置 Python 环境的依赖问题
TouchDesigner 自带的是 Python 3.9 环境,但它不是独立的 Python 解释器,很多第三方库默认没装。最常见的是 requests 和 websocket-client。如果你在 Script DAT 里 import requests 直接报 ModuleNotFoundError,就说明依赖没装。
解决办法有两种:一种是在 TouchDesigner 的 Python 环境里用 pip install 安装,但要注意 TouchDesigner 用的 Python 是它自带的,不是系统那个,直接命令行 pip 可能装错地方;另一种更推荐,在 TouchDesigner 里用 td.pip 工具,调用路径通常在 Dialogs -> Python Package Manager,或直接用:
python复制import subprocess
subprocess.run([sys.executable, "-m", "pip", "install", "requests", "websocket-client"])
sys.executable 在 TouchDesigner 里指向它内置的 Python,这样装才是装对地方。
3. 工作流提交阶段的典型报错与排查链路
环境搞定,连得上、依赖也有,接下来就是真刀真枪提交工作流。这个阶段的问题最多,我在这一节把排查链路完整走一遍,而不是只给结论。
3.1 请求 400 与 JSON 结构错误
POST /prompt 返回 400,基本是 JSON 结构不对。但具体哪里不对,需要一层层拆。
第一步,看 ComfyUI 的命令行窗口。ComfyUI 端会把具体的校验错误打到启动它的终端里,比如:
code复制ERROR: Prompt processing failed: 'nodes' is not allowed
如果看到这种信息,直接说明你把 UI 工作流当成 API 工作流交了。回到上一节,重新导出 API 格式。
第二步,检查节点 ID 的类型。API 格式里,最外层的 key 必须是字符串,不是数字。如果你在 TouchDesigner 里用 Python 字典直接构造,可能是整数 key,json.dumps 之后会变成 {"3": {...}},这没问题;但如果你是从别的地方复制了一份 UI 格式,里面节点 ID 可能是整数,ComfyUI 的校验器就会拒绝。
第三步,检查 inputs 里的引用格式。API 格式里,节点之间通过 ["节点ID", 输出索引] 这种数组引用,比如 "model": ["4", 0]。如果引用写错了,比如节点 ID 不存在、索引越界,ComfyUI 会报 ValidationError。这种错误在响应体里能看到详细原因,所以在 TouchDesigner 端不要只打印 status_code,要把 resp.text 也打出来。
3.2 提交成功但一直 queue 不动
这个现象比 400 更让人头疼:POST /prompt 返回了 200,也给了 prompt_id,但 ComfyUI 一直不执行,队列清空,图表不转。
大部分原因是把 UI 工作流提交上去了,ComfyUI 虽然接收了,但执行时发现格式不对,排队时直接跳过。也有小概率是 ComfyUI 在执行别的大任务,比如正在生成视频,新任务只能排队等待。
排查步骤:
- 在 ComfyUI 网页端刷新队列面板,看有没有任务卡住。
- 打开 ComfyUI 的命令行窗口,看有没有
Prompt executed相关日志。 - 在 TouchDesigner 端,把提交的 JSON 在本地用
json.loads(json.dumps(...))做一次序列化回读,看看结构和网页端 Network 面板里复制出来的是否一致。
我自己遇到过一次,原因是 TouchDesigner 的 DAT.text 拿到的字符串末尾被加了换行和空格,json.loads 竟然能解析成功,但提交后数据里混入了看不见的字符,ComfyUI 解析出奇怪的 key。后来所有工作流 JSON 我都用 json.loads 再 json.dumps 重新序列化一遍,彻底消除空白和编码差异问题。
3.3 缺 client_id 导致的回调丢失
这是我在开篇提到的问题。POST /prompt 时,请求体里可以带一个可选的 client_id 字段。如果你只是提交任务、不关心执行进度,这个字段无所谓;但如果你想通过 WebSocket 接收进度和完成通知,就必须带上 client_id,而且 WebSocket 连接地址里的 clientId 要和提交时用的完全一致。
很多第一次对接的人,提交时用的是随机 UUID,WebSocket 连接时又用了另一个 UUID,结果就是任务执行完了,ComfyUI 的 WebSocket 消息推给了那个"不存在的客户端",TouchDesigner 这边永远收不到。代码里看起来一切正常,其实消息发错人了。
正确做法:在 TouchDesigner 启动时生成一个固定的 client_id 字符串,比如 "td_client_main",提交和 WebSocket 都用它。这样就算消息偶尔丢了,也容易排查。
3.4 如何在 ComfyUI 端快速定位节点错误
工作流提交后如果报错,ComfyUI 网页界面会把报错节点标红,同时命令行窗口会给出 Python 堆栈。但 TouchDesigner 对接时,你很可能不在网页前,这时候可以主动去向 /history/{prompt_id} 查询执行状态:
python复制resp = requests.get("http://127.0.0.1:8188/history/{}".format(prompt_id), timeout=5)
data = resp.json()
返回的 data[prompt_id]["status"]["messages"] 里,会有一条 execution_error 类型的消息,里面的 exception_message 字段就是错误信息。这条信息会告诉你具体是哪个节点、什么原因导致的失败。把这个写进 TouchDesigner 的日志系统,调试效率会高很多。
另外一个实用技巧:在 ComfyUI 网页端,无论任何时候都能通过 ctrl + c 调出保存工作流的弹窗,里面有一个 copy 按钮,复制出来的是 API 格式。从这个 copy 按钮拿到的 JSON,是当前画布对应的 API 格式,这个比从文件菜单导出更直接,适合快速验证。
4. 图像回传与数据解析:base64、JSON 和 CHOP/DAT 的纠缠
任务执行成功了,接下来就是拿到生成好的图片。这个环节的坑主要集中在"怎么获取图片"以及"怎么把数据在 TouchDesigner 里变成可见内容"。
4.1 拿到图片的正确姿势
ComfyUI 的 WebSocket 消息里只会通知你"某个节点执行完了",并不会直接把图片的 base64 数据塞给你。要拿图,得走两步:
- 查询
/history/{prompt_id},从outputs里拿到图片的文件信息,包括文件名、子文件夹、类型(output还是temp)。 - 用
GET /view?filename=xxx&subfolder=xxx&type=output下载图片。
这个设计一开始会让人不习惯,但它的好处是灵活:你不一定非得在完成那一刻取图,任务跑完很久之后,依然可以通过 prompt_id 把历史结果捞出来。
在 TouchDesigner 里,我的建议是不要异步等待 WebSocket 再去下载,而是走一个更稳的流程:收到 WebSocket 完成的 executed 消息后,先 requests.get("/history/{}"),拿到图片路径,再直接下载到本地目录,然后用 Movie File In TOP 加载。
4.2 BASE64 解码常见失败场景
有些自定义节点会直接返回 base64 字符串,省去 .view 下载环节。但 base64 也有坑,最常见的是格式问题:有些节点返回的是纯 base64,有些返回的是带 data:image/png;base64, 前缀的 Data URL。
在 TouchDesigner 里解码时,如果不剥离前缀,直接 base64.b64decode,会报 Invalid base64-encoded string。正确做法是先判断前缀:
python复制import base64
b64_str = "..."
if b64_str.startswith("data:image"):
b64_str = b64_str.split(",", 1)[1]
img_data = base64.b64decode(b64_str)
还有一个场景容易被忽略:多张图片输出时,outputs 里的 image 可能是一个 list,每个元素有 filename、subfolder、type 三个字段。如果你只取了 filename,不处理 subfolder,那下载时就可能 404,因为 subfolder 为空时可以不传,但一旦非空,必须带上。
4.3 用 DAT 还是用 Python:TouchDesigner 解析 JSON 的取舍
TouchDesigner 原生的 JSON Parse DAT 可以把 JSON 转成表格,但 ComfyUI 的返回结构嵌套很深,转成表格后反而不如直接用 Python 字典直观。我的经验是:复杂 JSON 一律用 Python 处理,DAT 只负责显示最终字符串结果。
比如解析 /history 返回,用 Script DAT 里的 Python 直接操作:
python复制import requests, json
resp = requests.get("http://127.0.0.1:8188/history/{}".format(prompt_id), timeout=5)
data = resp.json()
outputs = data[prompt_id]["outputs"]
# 遍历所有输出节点
for node_id, node_output in outputs.items():
for img in node_output.get("images", []):
print(img["filename"], img["subfolder"], img["type"])
这样调试起来,任何一步都能打印出来确认。如果你非要用 DAT 的方式,也可以,但只建议在数据结构非常简单、层级不超过两层时使用。
4.4 多张图片输出的顺序与命名
如果你的 ComfyUI 工作流是一次性生成多张图,比如通过 Batch Prompt Schedule 或 Empty Latent Image 设置较大的 batch size,输出图片的文件名不会按直觉顺序排列。ComfyUI 的保存逻辑是按执行顺序递增,但多批次并发时顺序可能被打乱。
要保证顺序,有几种做法:
- 让 ComfyUI 输出到固定目录,TouchDesigner 端用文件修改时间排序加载。
- 在 ComfyUI 工作流里加一个自定义节点,把生成的图片名字写进返回的 metadata 里。
- 最简单粗暴:在 TouchDesigner 端不依赖文件名排序,每次生成完成后,先读文件列表,按修改时间最新的取。如果只需要一张图,这个逻辑最稳。
5. 性能、稳定性与交互节奏:从"能跑"到"能演"
TouchDesigner 对接 ComfyUI 这个组合,最大的痛点不是"能不能跑通",而是"演出现场能不能稳"。实时交互场景里,ComfyUI 的生成速度是瓶颈,TouchDesigner 如果无脑请求,系统很快就会崩。
5.1 请求排队与并发限制
ComfyUI 默认是单队列执行。你在 TouchDesigner 里每提交一个 POST /prompt,任务就进队列,ComfyUI 一个一个跑。如果 TouchDesigner 每一帧都提交,队列瞬间堆积几百个任务,ComfyUI 要跑几分钟才能消化,看起来就像卡死了。
所以在 TouchDesigner 端必须做并发控制。我的做法是维护一个"当前是否有任务在执行"的标志:
- 提交任务后,等 WebSocket 收到该任务完成的消息,才允许提交下一个。
- 用 Timer 或
run异步调度,限流提交频率,比如每秒最多提交一次。 - 如果用户触发了新的生成请求,而旧的还在队列里,可以主动调用
POST /queue清空旧队列,或者用POST /interrupt中断当前任务,再提交新的。
这个节奏设计,比代码细节更重要。从"每次点击都提交"改成"前一个跑完才允许下一次",稳定性会立刻上一个台阶。
5.2 超时、掉线与自动重连
TouchDesigner 的 WebSocket Client CHOP 或 DAT 在长连接断掉后,默认不会自动重连。ComfyUI 服务端偶尔会因为模型加载、内存不足或重启而断开连接,TouchDesigner 这边要能感知并重连。
我的方案是:做一个小脚本,每隔几秒检查 WebSocket 连接状态,如果断了,就尝试重新连接,重连成功后重新订阅。在 TouchDesigner 里,用 WebSocket Client DAT 的 onConnect、onDisconnect 回调事件触发重连逻辑。
python复制# 伪代码,在 TouchDesigner 的 WebSocket Client DAT 事件里
def onDisconnect(dat):
# 等待 2 秒后重连
run("op('websocket1').connect()", delayFrames=120)
这里有个细节:TouchDesigner 里 run 的延迟单位是帧,不是秒。在 60 FPS 的项目里,延迟 120 帧等于 2 秒。如果你是 30 FPS 的项目,同样的帧数就只有 4 秒延迟,要注意计算。
5.3 图片传输瘦身与格式选择
ComfyUI 默认保存 PNG 无损图,但 TouchDesigner 做实时渲染时对纹理要求并不需要这么高。如果你用 Movie File In TOP 加载,PNG 文件体积大、解码耗时,会造成每一帧加载卡顿。
优化方式:
- 在 ComfyUI 工作流里加一个
Image Save节点,设置输出质量为 JPEG,或缩小输出尺寸。 - 在 TouchDesigner 端下载图片后,用 Pillow 重新压缩成 JPEG 再加载。
- 如果只是做纹理,在 TouchDesigner 里用
Resolution TOP或Transform TOP把贴图缩小,不用原始分辨率。
这个优化对单张图影响不大,但如果做实时视频流,每帧或每几帧一张图,传输量直接决定了能不能跑得动。
5.4 预加载与缓存策略
第一次调用某个 checkpoint 模型时,ComfyUI 要把模型加载进显存,这个时间可能长达 10-20 秒。如果是演出,这个冷启动时间不可接受。解决办法很土但有效:项目初始化时,在 TouchDesigner 加载完成后,主动提交一个最简工作流,把常用模型预加载一遍。之后正式请求时,模型已经在显存里,生成速度会快很多。
另外,ComfyUI 自身有基于路径的缓存机制。如果两次请求的工作流完全相同(包括所有参数),ComfyUI 会复用第一次的 latent 结果,不再重新计算。这个特性可以用来做"生成结果预览":在正式提交前,用一个低分辨率版本先跑一遍,确认参数没问题,再以高分辨率正式提交。
6. 报错速查表与个人经验
这一节给一份可以直接对着排查的速查表,都是从实际项目和个人经验里沉淀出来的。
6.1 高频报错对照表
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
ConnectionRefusedError: [Errno 111] |
ComfyUI 没启动,或端口不对 | 确认 ComfyUI 进程运行,端口 8188;检查 ComfyUI 启动日志 |
ModuleNotFoundError: No module named 'requests' |
TouchDesigner 内置 Python 缺依赖 | 用 sys.executable -m pip install requests websocket-client 装入 TD 环境 |
HTTP 400 Bad Request |
JSON 不是 API 格式,或字段校验失败 | 用 ComfyUI 网页导出 API 格式,检查节点 ID 类型和引用数组格式 |
HTTP 404 on /history |
prompt_id 过期或 ComfyUI 重启过 | 检查 prompt_id 是否有效;重启后历史记录会清空 |
| 提交成功但 WebSocket 收不到完成消息 | client_id 不一致 | 提交和 WebSocket 连接使用同一个 client_id 字符串 |
Invalid base64-encoded string |
base64 字符串带 data URL 前缀 | 先剥离前缀再解码 |
model not found |
模型文件名/路径不匹配 | 从网页端 Network 面板复制实际提交的模型名 |
| 图片下载 404 | 缺少 subfolder 参数 | 从 history 输出的图片信息中带全三个字段 |
| ComfyUI 执行完但 TouchDesigner 没反应 | 消息顺序错乱或未按 prompt_id 过滤 | WebSocket 回调里判断 prompt_id 是否和当前任务一致 |
| 生成速度突然变慢 | 模型冷启动或显存不足 | 预加载模型;给 ComfyUI 加 --lowvram 参数;降低输出分辨率 |
6.2 我在实际项目中踩过的三个坑
第一个坑是忘了处理 HTTP 请求超时。默认 requests 的 timeout 是 None,也就是一直等。现场一旦 ComfyUI 卡住,TouchDesigner 的 Python 线程就挂在那,整个界面掉帧。后来所有请求都显式设置 timeout=10,宁可请求失败重新检查,也绝不让它无限等。
第二个坑是直接在 TouchDesigner 里用了 Movie File In TOP 加载 ComfyUI 输出目录,但 ComfyUI 每次生成的文件名如果相同,Movie File In TOP 不会重新加载。表面上看图片没更新,文件其实已经变了。解决方法是每次生成后修改文件名的访问时间,或者干脆给文件加时间戳,让 Movie File In TOP 每次拿到一个"新文件"。
第三个坑是没有区分 WebSocket 消息的执行顺序。ComfyUI 的 WebSocket 消息不是按节点执行顺序严格推送的,尤其是多客户端同时连接时,消息可能乱序。我在回调里只判断"收到了 executed 消息"就取结果,结果偶尔拿到的是上一次任务的结果。后来所有回调都按 prompt_id 过滤一遍,才彻底解决。
6.3 调试 ComfyUI 端的小技巧
最后分享几个提升调试效率的小技巧。
一个是在 ComfyUI 网页端按 F12 打开开发者工具,切到 Network 面板,然后手动跑一次工作流。你会看到浏览器和 ComfyUI 之间所有的请求和响应,包括 /prompt 提交的完整 JSON。这个 JSON 是最标准的 API 格式,直接复制到 TouchDesigner 里用,基本不会踩格式坑。
另一个是善用 ComfyUI 命令行窗口的日志。ComfyUI 会把每次执行的节点顺序、时间、错误堆栈都打出来。如果 TouchDesigner 端报错但信息不够,去命令行窗口看,基本都能发现端倪。比如模型加载失败,命令行里会明确写 File not found 或者路径不对。
还有一个和 TouchDesigner 相关的:把 ComfyUI 的返回数据先写到本地文本文件,再用 TouchDesigner 手动加载。这样可以把 ComfyUI 的问题和 TouchDesigner 的问题隔离开。我在项目里习惯把 prompt_id、status_code、history 的原始返回都写进一个 comfy_log.txt,出问题时先看这个文件,能省很多时间。
根据自己的经验,我要特别提醒一句:TouchDesigner 和 ComfyUI 的对接,不要追求"每次点击都实时生成"。ComfyUI 定位是批次生成或短间隔生成工具,不是低延迟实时渲染引擎。你把它的优势放在"生成高质量的关键帧"或"动态变化 prompt 时重绘"上,配合缓存、预加载和节流,整个项目才会真正稳定。哪怕演出时生成速度还是不够快,至少不会崩,这才是能上台的标准。
