Apollo 技术文档Apollo 技术文档
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
  • MMORPG 架构 QA

Q94: 如何实现热更新?

问题分析

本题考察对热更新的理解:

  • 热更新原理
  • 动态加载
  • 状态保持
  • KBEngine 脚本热更

一、热更新原理

1.1 热更新类型

┌─────────────────────────────────────────────────────────────┐
│                    热更新类型                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  配置热更新:                                                │
│  ├── 配置文件变化监听                                      │
│  ├── 无缝加载新配置                                        │
│  ├── 不影响运行                                              │
│  └── 示例: 日志级别调整                                      │
│                                                             │
│  代码热更新:                                                │
│  ├── Python 脚本重载                                          │
│  ├── Lua 脚本热更                                            │
│  ├── C++ 模块加载                                           │
│  └── 示例: Bug 修复、功能增加                                 │
│                                                             │
│  资源热更新:                                                │
│  ├── 图片/模型                                              │
│  ├── 配置数据                                              │
│  ├── 客户端资源                                              │
│  └── 示例: UI 素材更新                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、Python 脚本热更新

2.1 KBEngine 脚本热更新

# KBEngine Python 脚本热更新

"""
KBEngine 脚本热更新机制:

1. KBEngine 支持脚本的重新加载
2. 需要配置 reloadScripts
3. 热更新时状态会保持
"""

# KBEngine 配置
"""
<root>
    <reloadScripts>
        <!-- 是否启用脚本热更新 -->
        <enable>true</enable>

        <!-- 扫描间隔 (秒) -->
        <scanInterval>1.0</scanInterval>

        <!-- 需要热更的脚本目录 -->
        <scriptsPath>scripts/</scriptsPath>

        <!-- 排除的脚本 -->
        <exclude>
            <script>scripts/common/__init__.py</script>
        </exclude>
    </reloadScripts>
</root>
# scripts/__init__.py
# KBEngine 脚本入口

import KBEngine
import importlib

# 模块缓存
_module_cache = {}

def onInit(isReload):
    """
    初始化函数
    isReload: 是否是热更新调用
    """
    if isReload:
        INFO_MSG("Scripts reloading...")
    else:
        INFO_MSG("Scripts initializing...")

    # 注册热更新回调
    KBEngine.setCallback("onReload", onReloadCallback)

def onReloadCallback():
    """热更新回调"""
    INFO_MSG("Script files changed, reloading...")

    # 清理旧的模块缓存
    _cleanup_module_cache()

    # 重新加载模块
    _reload_modules()

    INFO_MSG("Scripts reloaded successfully")

def _cleanup_module_cache():
    """清理模块缓存"""
    global _module_cache

    for name in list(_module_cache.keys()):
        try:
            module = _module_cache[name]
            # 调用清理函数 (如果存在)
            if hasattr(module, 'onUnload'):
                module.onUnload()
            del sys.modules[name]
            del _module_cache[name]
        except:
            pass

def _reload_modules():
    """重新加载所有模块"""
    # 获取需要重新加载的模块列表
    modules_to_reload = _get_modified_modules()

    for module_name in modules_to_reload:
        try:
            if module_name in sys.modules:
                # 重新加载模块
                importlib.reload(sys.modules[module_name])

                INFO_MSG(f"Reloaded module: {module_name}")
        except Exception as e:
            ERROR_MSG(f"Failed to reload {module_name}: {e}")

def _get_modified_modules():
    """获取修改过的模块"""
    modified = set()

    # 检查 scripts 目录下的所有 .py 文件
    import os
    scripts_path = "scripts/"

    for root, dirs, files in os.walk(scripts_path):
        # 排除 __pycache__
        dirs[:] = [d for d in dirs if d not in ['__pycache__']]

        for file in files:
            if file.endswith('.py'):
                module_path = os.path.join(root, file)
                module_name = _path_to_module(module_path)

                # 检查文件修改时间
                mtime = os.path.getmtime(module_path)

                if module_name in _module_cache:
                    cached_mtime = _module_cache[module_name].get('mtime', 0)

                    if mtime > cached_mtime:
                        modified.add(module_name)

                # 缓存模块信息
                _module_cache[module_name] = {
                    'path': module_path,
                    'mtime': mtime
                }

    return modified

def _path_to_module(file_path):
    """将文件路径转换为模块名"""
    path = file_path.replace('/', '.')
    if path.startswith('scripts.'):
        path = path[8:]  # 移除 'scripts.'

    if path.endswith('.py'):
        path = path[:-3]  # 移除 '.py'

    return path

三、状态保持

3.1 状态序列化

# 状态序列化保持

class StatefulEntity(KBEngine.Entity):
    """有状态的实体"""

    def __init__(self):
        KBEngine.Entity.__init__(self)

        # 需要保持的状态
        self.state = "idle"
        self.target = None
        self.path = []
        self.inventory_items = {}

        # 状态版本
        self.state_version = 0

    def onReload(self):
        """热更新时调用"""
        # 保存当前状态
        old_state = self.serializeState()

        # 执行热更新后
        new_state = self.loadState(old_state)

        INFO_MSG(f"Entity {self.id} state reloaded: {old_state} -> {new_state}")

    def serializeState(self):
        """序列化状态"""
        return {
            "state": self.state,
            "target": self.target,
            "path": self.path,
            "inventory": self.inventory_items,
            "version": self.state_version
        }

    def loadState(self, state_data):
        """加载状态"""
        self.state = state_data.get("state", "idle")
        self.target = state_data.get("target")
        self.path = state_data.get("path", [])
        self.inventory_items = state_data.get("inventory", {})
        self.state_version = state_data.get("version", 0)

        return f"{self.state}|{self.target}"

四、C++ 模块热加载

4.1 动态库加载

// 动态库热加载

class HotReloadManager {
public:
    // 加载模块
    bool loadModule(const std::string& moduleName) {
        // 加载动态库
        HMODULE handle = LoadLibrary((moduleName + ".dll").c_str());

        if (!handle) {
            ERROR("Failed to load module: {}", moduleName);
            return false;
        }

        // 获取初始化函数
        using InitFunc = void(*)();
        InitFunc init = (InitFunc)GetProcAddress(handle, "initModule");

        if (!init) {
            FreeLibrary(handle);
            ERROR("Module missing initModule function: {}", moduleName);
            return false;
        }

        // 初始化模块
        init();

        modules_[moduleName] = handle;

        INFO("Module {} loaded successfully", moduleName);
        return true;
    }

    // 热重载模块
    bool reloadModule(const std::string& moduleName) {
        auto it = modules_.find(moduleName);
        if (it == modules_.end()) {
            ERROR("Module not loaded: {}", moduleName);
            return false;
        }

        HMODULE handle = it->second;

        // 获取清理函数
        using CleanupFunc = void(*)();
        CleanupFunc cleanup = (CleanupFunc)GetProcAddress(handle, "cleanupModule");

        if (cleanup) {
            cleanup();
        }

        FreeLibrary(handle);

        // 重新加载
        return loadModule(moduleName);
    }

private:
    std::unordered_map<std::string, HMODULE> modules_;
};

五、最佳实践

5.1 热更新建议

实践说明
状态保持热更后保持状态
版本兼容新版本兼容旧状态
灰度发布先部分服务器热更
回滚机制热更失败可回滚
日志记录记录热更操作

六、总结

热更新核心

热更新 = 状态序列化 + 动态加载 + 版本管理 + 灰度发布
- KBEngine 支持脚本热更
- 保持兼容状态
- 失败可回滚
- 充分测试

参考资料

  • Python Module Reloading
  • Hot Reload Best Practices
在 GitHub 上编辑此页
最后更新: 3/20/26, 6:06 AM
贡献者: cuihairu