游戏对象与资源管理:从内存泄漏到架构设计
1. 从一次内存泄漏说起为什么游戏对象管理值得单独拎出来讲几年前我参与过一个中型动作游戏的性能优化项目用的是自研引擎跑在移动端。上线前的压力测试里发现一个诡异现象连续切换关卡二十次之后内存占用会稳定上涨约80MB然后不再增长但只要再切几次又会跳一截。用内存快照工具抓下来一看堆里躺着大量已经死掉的角色对象——它们的引用计数明明归零了却依然挂在某个全局容器里没被释放。这个问题最后定位到资源管理模块的一个设计缺陷对象销毁时只解绑了逻辑层的引用却漏掉了资源层的一条反向索引。修复只花了半天但排查花了整整一周。这件事让我意识到游戏对象与资源管理这个看似基础设施的模块恰恰是引擎里最容易埋雷、也最能体现架构功力的地方。它不像渲染管线那样有炫目的画面产出也不像物理系统那样有直观的碰撞反馈但一旦设计得不好整个项目的内存、加载速度、热更新能力都会被拖垮。这篇内容我想聊的就是这个模块。它适合谁看如果你正在自己写引擎、正在给现有引擎做资源热更、或者单纯好奇为什么游戏里一个角色从创建到销毁背后发生了什么那这篇应该能给你一些可以直接抄作业的思路。我会从对象生命周期、资源引用模型、加载与卸载策略、以及实际项目里踩过的坑几个角度展开尽量把为什么这么设计讲透而不是只丢一堆名词。需要先说明一点不同引擎Unity、Unreal、自研在这块的实现差异极大我下面讲的很多方案是基于通用引擎架构的合理抽象具体到某个引擎时会有取舍。我会在关键处标注哪些是常见实践、哪些是我个人偏好。2. 游戏对象到底是什么从实体到句柄的抽象演进2.1 对象不是结构体而是一组能力的集合新手最容易犯的错是把游戏对象GameObject / Entity理解成一个大结构体里面塞满位置、血量、模型、AI状态。这种写法在Demo阶段没问题但一旦对象数量上千、类型上百就会变成一坨无法维护的泥巴。现代引擎普遍采用**组件化Component**的思路对象本身只是一个容器真正的数据和逻辑拆成一个个组件挂上去。这么做的核心理由是组合优于继承。假设你有玩家敌人NPC三种角色用继承你会得到一条Character - Player/Enemy/NPC的树但如果再加一个可驾驶的载具继承树立刻爆炸。组件化之后玩家 位置组件 渲染组件 输入组件 血量组件载具 位置组件 渲染组件 驾驶组件任意组合互不干扰。从内存布局角度看组件化还有一个隐性好处同类型组件可以连续存储这对CPU缓存极其友好。我实测过一个场景把1000个敌人的位置更新从对象数组遍历改成位置组件数组遍历帧耗时从1.8ms降到0.6ms纯粹是因为缓存命中率上去了。这就是所谓的**面向数据设计DOD**的雏形。2.2 句柄Handle才是对象在外部世界的真身对象在引擎内部可以是一个指针但绝对不要把裸指针暴露给游戏逻辑层。原因有三对象可能被销毁指针变野指针对象可能在内存里移动比如用了紧凑型内存池指针失效多线程环境下裸指针无法做安全校验。所以成熟引擎都会引入句柄这个概念。句柄本质上是一个带版本号的ID比如一个32位整数低24位是索引高8位是版本号。逻辑层拿到句柄后通过一个全局的句柄表去查真实对象。对象销毁时版本号加一这样即使旧句柄还留着查表时版本对不上直接判定为无效不会崩溃。// 一个简化的句柄结构 struct ObjectHandle { uint32_t index : 24; uint32_t version : 8; }; // 全局对象表 class ObjectTable { std::vectorObject* objects; std::vectoruint8_t versions; public: Object* resolve(ObjectHandle h) { if (h.index objects.size()) return nullptr; if (versions[h.index] ! h.version) return nullptr; // 已销毁 return objects[h.index]; } };这套机制的好处是安全性和可调试性。我在项目里加过一个调试开关每次resolve失败就打印调用栈结果抓出了好几处对象已销毁但逻辑还在跑的隐蔽bug。代价是每次访问多一次查表但这个开销在绝大多数场景下可以忽略。2.3 对象生命周期创建、激活、休眠、销毁一个游戏对象的完整生命周期通常比你想的要长。以我参与过的项目为例一个敌人从被策划配置在关卡里到最终从内存消失中间会经历定义阶段策划在编辑器里配置此时只有数据没有运行时对象。实例化阶段关卡加载时根据配置创建对象分配内存挂载组件。激活阶段对象进入玩家视野或逻辑范围开始执行Update。休眠阶段对象离开视野暂停Update但保留内存等待可能的重入。回收阶段对象确定不再需要标记为待销毁。销毁阶段真正释放内存句柄版本号加一。很多性能问题就出在激活和休眠的边界判断上。我见过一个项目敌人只要离开摄像机就休眠结果玩家一转身敌人就复活在原地因为休眠时把AI状态也冻结了。正确的做法是视觉休眠和逻辑休眠要分开视觉可以停但AI的计时器、Buff倒计时这些逻辑该跑还得跑只是不渲染而已。3. 资源管理的核心矛盾谁持有、谁释放、什么时候3.1 引用计数不是银弹但它是起点资源管理最经典的方案是引用计数每个资源维护一个计数器被引用时加一引用解除时减一减到零就卸载。听起来很美好但实际项目里会碰到两个大坑。第一个坑是循环引用。A资源引用了BB又引用了A两者计数永远不归零内存泄漏。解决办法通常是引入弱引用Weak Reference弱引用不加计数只在需要时尝试升级为强引用。我在项目里规定跨模块的引用一律用弱引用只有模块内部的短生命周期引用才用强引用这样循环引用基本被杜绝。第二个坑是计数抖动。如果每帧都有人加引用、有人减引用计数器频繁变化不仅性能差还容易在多线程下出竞态。我的经验是引用计数的粒度要粗。比如一个角色引用了模型、贴图、动画三份资源不要每帧去加减而是在角色创建时一次性加销毁时一次性减。中间如果需要换装再单独处理换装那部分。3.2 资源加载的三种时机预加载、按需加载、流式加载资源什么时候加载直接决定了游戏的加载体验和内存峰值。三种策略各有适用场景策略触发时机优点缺点适用场景预加载关卡开始前运行时无卡顿加载慢、内存峰值高小关卡、Boss战按需加载首次使用时加载快、内存省首次使用有卡顿开放世界探索流式加载后台持续加载体验平滑实现复杂、需管理优先级大型无缝地图我个人的偏好是混合策略核心资源玩家模型、UI、常用音效预加载场景资源按需加载但配合一个预读机制——根据玩家位置和朝向提前加载前方一定范围内的资源。这个预读半径需要根据移动速度调我一般设成玩家3秒内可能到达的区域实测下来既能避免卡顿又不会浪费太多内存。流式加载的实现细节很多核心是优先级队列 分帧加载。每帧只加载固定预算的资源比如2MB剩下的排队。优先级怎么定我的做法是距离玩家越近优先级越高视线正前方的优先级高于背后已经进入视野但还没加载完的优先级最高因为再不加载就要穿帮了。3.3 资源卸载比加载更难的是什么时候能卸加载是显式的卸载却往往是隐式的——你很难确定一个资源是不是真的没人用了。引用计数能解决大部分问题但还有几种情况需要额外处理缓存策略有些资源卸载后很快又会被加载频繁IO反而更慢。所以通常会保留一个LRU缓存最近用过的资源即使计数归零也先留着等内存紧张时再按最久未使用顺序清理。平台差异移动端内存紧张缓存要小主机端内存充裕可以多留。我一般会按平台配置不同的缓存上限移动端设64MB主机端设256MB。卸载时机绝对不要在游戏逻辑执行中间卸载资源否则可能出现正在用的资源突然没了。我的做法是延迟到帧末统一处理卸载队列确保当前帧所有逻辑都跑完了再释放。这里有个血泪教训曾经有个项目在切场景时同步卸载旧场景资源结果卸载过程中触发了某个对象的析构析构里又去访问了另一个已卸载资源直接崩溃。后来改成两阶段卸载先标记帧末统一释放问题消失。4. 对象与资源的绑定关系一场关于解耦的持久战4.1 对象不该直接持有资源指针这是我最想强调的一条设计原则游戏对象不应该直接持有资源的裸指针。原因很简单资源可能被卸载、可能被替换、可能被热更新对象如果直接拿着指针这些操作都会变得极其危险。正确的做法是让对象持有资源句柄通过句柄去间接访问。这样资源替换时只需要更新句柄表里的映射对象完全无感。热更新时更是如此——新资源加载进来句柄指向新资源旧资源等引用归零后自然卸载整个过程对象层不需要改一行代码。class RenderComponent { ResourceHandle meshHandle; // 不是 Mesh* ResourceHandle materialHandle; public: void render() { Mesh* mesh ResourceManager::instance().getMesh(meshHandle); if (!mesh) return; // 资源还没加载好跳过 // ... 渲染逻辑 } };注意上面那个if (!mesh) return;这是异步加载的必然产物。资源可能是异步加载的对象创建时资源还没到位所以渲染时要判空。很多新手会在这里踩坑忘了判空导致崩溃。4.2 资源依赖一个模型背后可能是一棵树一个看似简单的模型资源背后可能依赖贴图、材质、骨骼、动画、甚至着色器。这些依赖关系必须被引擎自动管理否则策划配一个模型程序要手动加载十几份资源迟早出错。我的做法是给每种资源定义依赖描述加载时递归解析。比如模型依赖材质材质依赖贴图和着色器加载模型时自动把整棵树拉起来。卸载时反向操作但要注意共享依赖——两个模型用同一张贴图卸载一个模型时不能把贴图也卸了得靠引用计数兜底。这里有个优化点依赖树可以预计算并缓存。第一次加载某类资源时解析依赖并记录后续同类资源直接查表省去重复解析。我在项目里做过统计预计算依赖树让资源加载的CPU耗时降低了约30%。4.3 热更新场景下的对象与资源一致性热更新是资源管理最复杂的场景之一。假设玩家正在游戏中你推送了一个新版本的贴图怎么保证不出问题核心原则是新旧资源共存逐步切换。具体流程新资源下载到本地但不立即生效。在合适的时机比如切场景、读条把句柄表里指向旧资源的条目更新为新资源。旧资源引用计数归零后卸载。绝对不要在对象正在使用时强行替换资源那会导致渲染错乱甚至崩溃。我见过一个项目为了实时热更在渲染线程正在读贴图时替换了贴图指针结果画面撕裂了一整帧。后来改成双缓冲渲染用旧资源逻辑层准备新资源下一帧切换问题解决。5. 内存池与对象复用别让GC成为你的帧率杀手5.1 为什么游戏引擎很少用通用GC通用垃圾回收比如Java、C#的GC在游戏里是灾难性的因为它的停顿时间不可控。你永远不知道GC什么时候触发一触发可能就是几十毫秒的卡顿对于60帧的游戏来说一帧只有16.6ms一次GC直接掉好几帧。所以游戏引擎普遍采用手动内存管理 对象池。对象池的思路很简单预先分配一批对象用的时候从池里取不用的时候还回去而不是频繁new/delete。这样内存分配次数大幅减少也避免了内存碎片。templatetypename T class ObjectPool { std::vectorT* freeList; public: T* acquire() { if (freeList.empty()) { return new T(); // 池空了才真正分配 } T* obj freeList.back(); freeList.pop_back(); return obj; } void release(T* obj) { obj-reset(); // 重置状态 freeList.push_back(obj); } };5.2 对象池的坑状态重置不干净对象池最大的坑是状态残留。一个敌人对象被回收后如果血量、Buff、AI状态没清干净下次从池里取出来时就会带着上次的记忆出现各种诡异行为。我踩过的最离谱的一次是一个子弹对象复用时上一发的飞行方向没重置结果新子弹一发射就朝反方向飞。解决办法是给每个可复用对象实现一个严格的reset方法把所有成员变量恢复到初始值。更稳妥的做法是用placement new重新构造虽然有点开销但能保证状态绝对干净。我在性能敏感的场景用reset在逻辑复杂的场景用placement new两者结合。5.3 内存池的粒度选择内存池不是越大越好也不是越小越好。粒度太细池的管理开销超过分配本身粒度太粗内存浪费严重。我的经验值是按对象类型分池每个池的块大小固定。比如子弹池每块64字节敌人池每块512字节。如果某类对象大小差异很大就再细分几个池。另外池的初始容量要根据场景预估。我一般会统计关卡里某类对象的最大同时存在数量池容量设成这个值的1.2倍。太小会频繁触发真实分配太大浪费内存。这个值可以在运行时动态调整——如果发现池经常空就扩容如果长期空闲就缩容。6. 多线程下的对象与资源管理锁不是唯一答案6.1 资源加载天然适合多线程资源加载是IO密集型任务非常适合放到后台线程。主线程只管逻辑后台线程负责把资源从磁盘读进内存、解码、上传到GPU。这样主线程不会被IO阻塞帧率更稳定。但多线程带来一个新问题资源加载完成时主线程可能正在用这个资源。解决办法是加载完成回调 主线程合并。后台线程加载完后把资源放进一个待合并队列主线程在帧末统一处理这个队列把资源正式注册到资源管理器。这样避免了跨线程直接操作资源表。6.2 对象更新的并行化对象更新Update能不能并行可以但要小心数据依赖。如果两个对象互相读写对方的数据并行就会出竞态。我的做法是按组件类型分批并行先并行更新所有位置组件再并行更新所有AI组件以此类推。同一批次内组件互不依赖可以安全并行。// 伪代码分阶段并行更新 parallel_for_each(positionComponents, [](auto c) { c.update(); }); parallel_for_each(aiComponents, [](auto c) { c.update(); }); parallel_for_each(renderComponents, [](auto c) { c.update(); });这种阶段化并行的另一个好处是缓存友好——同一类组件连续存储并行时每个线程处理一段连续内存缓存命中率高。6.3 无锁队列在资源管理中的应用资源加载完成的通知我倾向于用无锁队列而不是加锁队列。因为加载线程可能很频繁地往队列里塞消息加锁会成为瓶颈。无锁队列用原子操作实现单生产者单消费者场景下性能极佳。不过无锁队列的实现细节很容易出错我建议直接用成熟库不要自己造轮子。自己实现的话至少要处理ABA问题、内存序问题稍不注意就是偶发的诡异bug极难调试。7. 实战踩坑记录那些文档里不会写的教训7.1 坑一资源句柄的版本号溢出前面说句柄用8位版本号意味着同一个索引最多复用256次。如果某个资源被频繁加载卸载版本号会回绕导致旧句柄复活——明明已经销毁的对象旧句柄突然又能查到了。这个bug极其隐蔽因为要复用256次才触发。解决办法版本号用16位甚至32位或者索引不复用每次分配新索引索引空间用完再整体整理。我现在的项目用32位版本号基本不可能溢出。7.2 坑二异步加载的回调地狱早期我做异步加载时每个资源加载完都要写一个回调回调里可能又要加载依赖资源层层嵌套代码变成金字塔。后来改成Promise/Future模式用链式调用替代嵌套回调代码清爽很多。loadResourceAsync(model.a) .then([](Resource r) { return loadDependency(r); }) .then([](Dependency d) { /* ... */ });7.3 坑三卸载顺序导致的悬空引用资源之间有依赖关系卸载时必须先卸依赖者再卸被依赖者。如果顺序反了被依赖者先卸载依赖者还在用就悬空了。我的做法是拓扑排序根据依赖图算出卸载顺序确保安全。7.4 坑四编辑器与运行时的资源路径不一致编辑器里资源路径可能是绝对路径打包后变成相对路径如果不做统一处理运行时就会找不到资源。我的经验是所有资源路径在引擎层统一成虚拟路径由资源管理器负责映射到实际路径编辑器、运行时、热更各有一套映射规则互不干扰。8. 一套可落地的最小实现思路如果你现在要自己实现一套对象与资源管理我建议从最小可用版本开始不要一上来就追求完美。我的推荐路径第一版对象用ID管理资源用引用计数同步加载。能跑通就行。第二版加入对象池资源加载改成异步加一个简单的LRU缓存。第三版引入句柄和版本号解决悬空引用问题。第四版加入依赖管理、热更新支持、多线程加载。第五版性能优化内存池调优无锁队列。每一版都要有压力测试模拟大量对象创建销毁、频繁资源加载卸载观察内存和帧率。我见过太多项目在Demo阶段跑得好好的一上量就崩就是因为没做压力测试。最后分享一个我个人的小习惯给资源管理器加一个调试面板实时显示当前加载的资源数量、内存占用、缓存命中率、加载队列长度。这个面板在排查性能问题时极其有用比看日志直观一百倍。我在项目里甚至加了一个资源泄漏检测按钮一键对比两次快照找出只增不减的资源定位泄漏点。这套东西没有银弹每个项目都要根据自己的场景调。但只要你理解了对象生命周期、引用模型、加载卸载时机这几个核心概念剩下的就是工程上的取舍和打磨了。

相关新闻

端侧AI样机验收:超维方程五项工程检查实战指南

端侧AI样机验收:超维方程五项工程检查实战指南

搞端侧 AI 的都知道,样机验收永远比开发更难做。我前后经手过七八款带 NPU 的样机,从摄像头模组到工业质检设备都碰过,踩过的坑足以攒成一箩筐。这几年团队沉淀了一套验收方法论,内部叫“超维方程”,核心就一句话&…

2026/10/13 23:15:20 阅读更多 →
USB转串口驱动失效排查:从VID/PID识别到跨平台固化配置

USB转串口驱动失效排查:从VID/PID识别到跨平台固化配置

简介:本资源是面向嵌入式开发、硬件调试及系统运维工程师的通用USB转串口驱动合集,解决各类USB Serial Converter设备在多平台下的识别与通信问题,尤其适用于Linux内核模块编译、Windows驱动签名适配、Mac OS终端调试等典型场景。压缩包共273…

2026/10/13 23:14:20 阅读更多 →
MooTool Next Compose:用 Compose Multiplatform 构建独立桌面工具集的工程实践

MooTool Next Compose:用 Compose Multiplatform 构建独立桌面工具集的工程实践

桌面应用开发工具 【免费下载链接】MooTool Handy tool set for developers 项目地址: https://gitcode.com/gh_mirrors/mo/MooTool 点击查看 免费下载 本文基于仓库中的 next-compose/README.md 展开,系统讲解 MooTool 的 Compose Multiplatform Deskt…

2026/10/13 23:14:20 阅读更多 →

最新新闻

Qt迷宫生成与路径搜索实战:BFS/A*算法及可视化实现

Qt迷宫生成与路径搜索实战:BFS/A*算法及可视化实现

简介:Qt随机迷宫及路径获取工程是一份结合Qt图形界面、并查集与A 寻路算法的完整示例,适合Qt学习者和算法入门者参考。资源为RAR压缩包,共66个文件,以cpp/h源文件、exe可执行程序、dll运行库、png位图资源及qss样式文件为主&…

2026/10/14 0:13:45 阅读更多 →
GrabCut MATLAB图像分割实战:参数调优与避坑指南

GrabCut MATLAB图像分割实战:参数调优与避坑指南

简介:这份资源面向计算机视觉初学者与图像处理方向的学生、开发者,提供在MATLAB环境中实现GrabCut图像分割算法的完整代码示例。GrabCut基于马尔科夫随机场与高斯混合模型,通过图割迭代优化前景背景标签,适合需要交互式区域分割、…

2026/10/14 0:13:45 阅读更多 →
谱减法去噪基础版:从原理到代码的语音增强入门实践

谱减法去噪基础版:从原理到代码的语音增强入门实践

音频去噪是个越做越有意思的方向,而且我越来越觉得,入门语音增强最好的路径不是直接堆模型,而是先把经典的谱减法跑通一遍。最近我把“谱减法去噪基础版”从算法到代码完整实现了一遍:读音频、分帧加窗、FFT、噪声估计、谱减、逆变…

2026/10/14 0:13:45 阅读更多 →
QOwnNotes 在 Gentoo Linux 上的安装指南:overlay 仓库配置与密钥环故障排查

QOwnNotes 在 Gentoo Linux 上的安装指南:overlay 仓库配置与密钥环故障排查

桌面应用 【免费下载链接】QOwnNotes QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration. 项目地址: https://gitcode.com/gh_mirrors/qo/QOwnNotes 点击查看 免费下载 QOwnNotes 是一款…

2026/10/14 0:13:45 阅读更多 →
Apache Mesos 内存剖析实战:基于 Jemalloc 的 Heap Profiling 与 HTTP 端点全解

Apache Mesos 内存剖析实战:基于 Jemalloc 的 Heap Profiling 与 HTTP 端点全解

云原生 【免费下载链接】mesos Apache Mesos 项目地址: https://gitcode.com/gh_mirrors/mesos2/mesos 点击查看 免费下载 本文基于 Apache Mesos 官方文档 memory-profiling.md 与 libprocess 的 MemoryProfiler 源码实现展开,系统讲解如何在 Linux 上…

2026/10/14 0:13:45 阅读更多 →
GoModel 技术哲学解析:Postel定律、优秀默认值与Go语言架构决策全解

GoModel 技术哲学解析:Postel定律、优秀默认值与Go语言架构决策全解

【免费下载链接】GoModel AI gateway / AI control plane / AI proxy written in Go. Unified OpenAI-compatible and Anthropic-compatible API for OpenAI, Anthropic, Gemini, Groq, xAI, Ollama, vLLM and more. A LiteLLM alternative with observability, guardrails, st…

2026/10/14 0:12:45 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →