游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载本指南以 sol2-3.3.0 官方文档《errors》为核心骨架系统讲解在 Piccolo 引擎内置 sol2 与 Lua 5.4中如何检测脚本运行错误、处置 panic 崩溃、规避 C/Lua 混合编程下的编译与链接陷阱并给出可直接复制的代码与编译参数。读完你将掌握safe_script家族 API、script_default_on_error/script_pass_on_error两个内置错误处理器、SOL_USING_CXX_LUA与SOL_EXCEPTIONS_SAFE_PROPAGATION等关键宏的使用边界以及一套从报错到定位的完整排查方法论。一、sol2 在 Piccolo 引擎中的实际角色Piccolo 引擎原 Pilot在engine/3rdparty/目录下以源码形式内置了 Lua 5.4.4、sol2-3.3.0 以及 sol2 所需的绑定代码并封装出一个 Lua 组件供游戏对象使用。在 lua_component.cpp 中可以看到典型的 sol2 集成方式// 打开 base 库向 Lua 暴露 C 函数与对象 m_lua_state.open_libraries(sol::lib::base); m_lua_state.set_function(set_float, LuaComponent::setfloat); m_lua_state.set_function(get_bool, LuaComponent::getbool); m_lua_state.set_function(invoke, LuaComponent::invoke); m_lua_state[GameObject] m_parent_object; // 每帧执行 Lua 脚本 m_lua_state.script(m_lua_script);其中m_lua_state的类型是 sol::state见 lua_component.h。这里直接调用了script而官方文档 errors.rst 正是围绕脚本可能出错这一现实展开的脚本可能语法错误、可能从文件系统加载失败、可能运行期抛错如果不做区分和处理轻则静默失败重则直接拖垮整个 Lua VM。下面逐一展开。二、脚本运行错误的三层排查safe_script 与内置错误处理器2.1 为什么会分 safe / unsafe 两套sol2 在 state_view.hpp 中为sol::state/sol::state_view提供了两组脚本执行接口safe 系safe_script、safe_script_file。加载并执行脚本出错时走错误回调返回sol::protected_function_result可检查valid()。unsafe 系unsafe_script、unsafe_script_file。出错时直接调用lua_error进而触发 panic不做任何检查性能最快但最危险。默认的script/script_file在定义SOL_SAFE_FUNCTION_OBJECTS或SOL_ALL_SAFETIES_ON时等价于 safe 版本否则等价于 unsafe 版本。官方文档给出的建议很明确只要不是刻意追求极致速度请优先使用safe_script系列。2.2 内置错误处理器default / pass / throwsafe_script的最后一个参数是仅当出错时才被调用的错误回调。sol2 预置了两个默认处理器定义见 state_handling.hppsol::script_pass_on_error(lua_State*, protected_function_result)原样透传protected_function_result不抛异常、不触发 panic把错误判断完全交给调用方sol::script_default_on_error(lua_State*, protected_function_result)默认处理器。在未定义SOL_DEFAULT_PASS_ON_ERROR时实际转发给script_throw_on_error——它会拼装一条包含call_status、异常类型与what()文本的错误信息然后抛出sol::error若定义了SOL_DEFAULT_PASS_ON_ERROR则退化为 pass 行为官方在 safety.rst 中强烈警告该宏非常危险除非有极其充分的理由否则不要开启。2.3 三种错误处理方式完整对照官方示例 script_error_handling.cpp 用同一段坏脚本bad$#*$syntax演示了三种策略方式一default_on_error try/catch异常可用时try { int value lua.script(code, sol::script_default_on_error); // 脚本错误这里永远不会到达 std::cout value std::endl; } catch (const sol::error err) { std::cout Something went horribly wrong: thrown error \n\t err.what() std::endl; }方式二pass_on_error 结果检查不依赖异常sol::protected_function_result result lua.script(code, sol::script_pass_on_error); if (!result.valid()) { sol::error err result; sol::call_status status result.status(); std::cout sol::to_string(status) error \n\t err.what() std::endl; }方式三load 分步执行最低层、最显式sol::load_result loaded_chunk lua.load(code); if (!loaded_chunk.valid()) { // 语法错误在此暴露 sol::error err loaded_chunk; sol::load_status status loaded_chunk.status(); std::cout loading error: sol::to_string(status) \n\t err.what() std::endl; } else { sol::protected_function script_func loaded_chunk.getsol::protected_function(); sol::protected_function_result result script_func(); if (!result.valid()) { // 运行期错误在此暴露nil 访问、内存错误等 sol::error err result; sol::call_status status result.status(); std::cout running error: sol::to_string(status) \n\t err.what() std::endl; } }2.4 自定义错误回调只会在出错时被调用示例 state_script_safe.cpp 展示了自定义回调的写法。回调收到protected_function_result你可以自由处理甚至再次抛出但若选择不抛出就必须把结果return回去auto result2 lua.safe_script(123 bad.code, [](lua_State*, sol::protected_function_result pfr) { sol::error err pfr; std::cout An expected error occurred: err.what() std::endl; // 不抛异常就必须返回结果 return pfr; });底层实现state_view.hpp也很直白safe_script先调用do_string/do_reader/do_file加载执行若结果!pfr.valid()才调用on_error(L, std::move(pfr))否则原样返回。这保证了回调只在真正出错时触发正常情况下没有任何额外开销。三、编译器错误与警告一份可对照的排查清单sol2 是重度模板库错误信息往往又长又吓人。官方文档给出了 8 条实战经验逐一展开如下3.1 先检查 C 标准开关如果报错堆里满是std::index_sequence、type traits 等std::成员相关错误十有八九是没有开启 C14。Visual Studio 2015 起默认开启但 g / clang 需要显式指定--stdc14 # 或早期编译器使用 --stdc1y3.2 非基本类型入栈报出 initializer list / luaL_Reg 错误向 Lua 推送非基本类型时若出现奇怪的 initializer list 或无法初始化luaL_Reg的错误多半与 sol2 的自动注册机制automagical registration为 usertype 自动生成常见运算符与成员访问方法有关。可尝试通过#define SOL_DEFAULT_AUTOMAGICAL_USERTYPES 0关闭默认的 automagical 行为相关开关定义见 version.hpp关闭后自行显式注册需要的成员。3.3 大量成员绑定导致的调试符号截断警告当单个 usertype 绑定了大量成员函数时生成的代码可能触发debug symbols cut off或__LINE_VAR超长等警告。这类警告在多数编译器下可以安全屏蔽例如 MSVC 的/wd系列或对应 warning level 调整。3.4 模板深度错误早期 clang / g 上常出现模板实例化深度超限。用-ftemplate-depth把深度提到足够高即可-ftemplate-depth2048 # 甚至更高3.5 MSVC C1128需要 /bigobj大量 usertype 模板实例化会让 MSVC 触发compiler error C1128目标文件段数超限解决办法是加编译标志/bigobj。3.6 move-only 类型必须 readonly如果绑定的是 move-only 类型如std::unique_ptr成员当它作为 usertype 的成员变量、或用state_view::set_function绑定时需要包一层sol::readonly。示例见 usertype_advanced.cppplayer_type.set(bullets, sol::readonly(player::bullets));此时 Lua 侧只能读不能写写入会报错。sol::readonly的详细说明见 readonly.rst。3.7 std::string / std::pair 的 operator 赋值错误std::string或std::pairT1, T2在构造后用operator赋值在与sol::function及其返回值交互时可能产生编译错误官方在 issue 中给出的修复方式是对这类返回值显式取类型或改用显式构造避免隐式 operator 路径。3.8 __stdcallx86与 /std:clatest 的坑在 32 位 (x86) 环境下VC 的编译器 bug 可能导致__stdcall函数绑定失败。sol2 已带初步修复若仍有问题把函数塞进std::function再绑定通常能绕开。若使用/std:clatest并出现大量针对函数noexcept说明符的报错说明当前 VC 编译器版本对最新标准支持尚不完善需要等待编译器更新或向编译器厂商反馈。四、Mac OSX 下 LuaJIT 的随机崩溃在 Mac OSX 上使用 LuaJIT 时代码可能在看似随机的位置崩溃。官方建议按 LuaJIT 官网说明在构建中加入如下链接标志-pagezero_size 10000 -image_base 100000000这两个标志能让 LuaJIT 的 JIT 代码在 macOS 的地址空间布局下正常运行避免任意崩溃。注意本项目默认内置的是 PUC-RIO Lua 5.4.4见 lua-5.4.4 目录此条主要面向自行替换为 LuaJIT 的场景。五、compiler out of heap space让 VS 编译器使用 x64 工具链这是 Visual Studio 的典型问题VS 默认的编译器本身是 32 位可执行程序它照样能产出 x86 / x64 / ARM 二进制因此宿主进程的堆空间受限。按官方指引源自社区 issue 中 OrfeasZ 的修复方案在.vcxproj的Import .../语句之后加入PropertyGroup PreferredToolArchitecturex64/PreferredToolArchitecture /PropertyGroup让编译器默认使用 64 位工具堆空间上限随之提升到 64 位 Windows 机器可承载的水平。如果内存仍不足例如机器不足 4GB RAM就要考虑拆分 usertype把一个大 usertype 分散到多个 C 文件不要在单个new_usertype(...)调用里塞全部函数改用下标赋值逐个注册auto my_usertype lua.new_usertypemy_class(my_class); my_usertype[talkin] about; my_usertype[this] my_class::that; my_usertype[and_the] sol::property(my_class::third);逐条my_usertype[func] func;的方式能显著降低单次模板实例化的压力是官方文档明确推荐的写法。六、链接错误C 与 C 调用约定之争6.1 extern C 的默认假设sol2 假定 Lua 库按 C 约定编译因此在其包含代码里用extern C包裹了 Lua 头文件静态/动态链接的 Lua 通常都导出 C 符号。但如果目标 Lua 库是用 C 编译的就必须去掉extern C块、切换到 C 的名称修饰name mangling否则链接阶段找不到符号。6.2 SOL_USING_CXX_LUA / SOL_USING_CXX_LUA_JIT在包含sol/sol.hpp之前或加入编译命令行定义#define SOL_USING_CXX_LUA即可告知 sol2Lua 是 C 编译的。若用的是以 C 模式构建的 LuaJIT还需额外定义SOL_USING_CXX_LUAJIT官方原话基本不会有人这么干通常不需要。标准构建的 Lua / LuaJIT 都不应该定义这些宏否则反而会破坏 C 链接假设。这两个宏的实际效果记录在 safety.rst 的 Linker Config 一节取消extern C包裹、自动打开SOL_EXCEPTIONS_SAFE_PROPAGATION等。七、caught (...) exception异常被吞掉的问题有时你期望捕获到精心构造的错误实际却只收到一个catch(...)级别的通配异常。这通常意味着你把 Lua 构建成了 C或你正在使用对 C 异常有完整互操作支持的框架LuaJIT 在特定平台LuaJIT 2.0.5 的 x64LuaJIT 2.1.x-beta 及之后的 x86 与 x64。此时请在包含 sol2 前定义#define SOL_EXCEPTIONS_SAFE_PROPAGATION该宏的语义见 safety.rst不再在 C 函数 trampoline 与safe_/protected_function内部使用 try/catch 包裹让 C 异常直接穿透 Lua API 传播回来从而在 C 侧用catch (...)捕获到真正的异常。注意 exceptions.rst 中的警告当检测到 LuaJIT 时 sol2 不会自动定义该宏必须由你确认平台支持后自行定义。实现层面该宏最终映射为SOL_PROPAGATE_EXCEPTIONS见 lua_version.hpp并在两处改变行为函数 trampolinetrampoline.hpp与protected_function的调用核心protected_function.hpp——后者在开启时跳过try/catch分支让异常原样上抛。八、panic 与崩溃捕获错误不等于一切安好8.1 default_at_panic 做了什么sol2 在创建状态时会自动安装default_at_panic处理器见 state_handling.hpp异常未关闭时它取出栈顶错误消息若定义了SOL_PRINT_ERRORS会打印到std::cerr然后throw sol::error(err)给 C 侧一个恢复机会。完整的状态初始化流程封装在set_default_state中state_handling.hpp它会依次设置 panic 函数、默认 traceback 处理器、异常处理器并注册主线程。8.2 为什么官方强烈建议捕获 崩溃关键警告来自 errors.rst 的Catch and CRASH!一节Lua 调用lua_atpanic意味着 VM 已经处于不可预测甚至死亡的状态。Lua 本质上是 C API异常从它里面冒出来属于最后的、终止性的行为。在默认处理器抛出的异常上做try { ... } catch (...) {}后继续运行等于在一个状态不明的 VM 上继续执行——release/优化模式下会出现难以排查的诡异 bug。官方给出的推荐流程是记录日志 → 尽快终止当前 Lua VM → 若应用无法新建一个 Lua state 则直接崩溃。set_panic的签名在 state.rst 中有说明它是void set_panic(lua_CFunction panic)必须匹配int(lua_State*)签名。8.3 自定义 panic 函数示例若你不想让默认处理器抛异常例如异常被关闭时可用sol::state lua(my_panic_function)构造、lua_atpanic(L, fn)或state_view::set_panic自行注册。官方示例 my_panic.cppinline void my_panic(sol::optionalstd::string maybe_msg) { std::cerr Lua is in a panic state and will now abort() the application std::endl; if (maybe_msg) { std::cerr \terror message: maybe_msg.value() std::endl; } // 函数返回后 Lua 将按默认行为 abort() } int main() { sol::state lua(sol::c_calldecltype(my_panic), my_panic); // 等价写法 // lua_atpanic(L, sol::c_calldecltype(my_panic), my_panic); // lua.set_panic(sol::c_calldecltype(my_panic), my_panic); return 0; }九、析构函数与安全为什么建议把 Lua 构建为 CLua 是 C API出错时依赖setjmp/longjmp跳出代码。longjmp不会触发 C 析构函数——如果你错误地使用了库或底层 Lua VM栈上的 RAII 对象可能被直接跳过造成资源泄漏与未定义行为。官方建议把 Lua 构建为 C。此时lua_error以 C 异常的形式上抛触发正常的栈展开stack unwinding析构函数得以按标准语义执行。这与第七节SOL_USING_CXX_LUA的设定是配套的——用 C 构建 Lua 并定义该宏既解决链接约定又获得正确的异常展开语义。十、受保护函数与安全取值日常开发的默认姿势10.1 默认 API 的乐观假设errors.rst 明确指出默认情况下sol::function假设代码运行完美无缺sol::state(_view)::script(_file)同样如此——它们追求简单与速度出错时以抛异常暴力终止 VM。因此在生产代码里应改用sol::protected_function调用结果可检查valid()/status()/ 错误消息sol::optionalT从 Lua 安全取值取不到时为nulloptdo_string/do_file/load/load_file安全加载并拿到结果。10.2 protected_function 实战示例 protected_functions.cpp 展示了带错误处理函数的受保护调用lua.script(R( function handler (message) return Handled this message: .. message end function f (a) if a 0 then error(negative number detected) end return a 5 end )); sol::protected_function f(lua[f], lua[handler]); sol::protected_function_result result f(-500); if (result.valid()) { int x result; std::cout call succeeded, result is x std::endl; } else { sol::error err result; std::cout call failed, what(): err.what() std::endl; // what() 将得到 Handled this message: negative number detected }其实现核心是lua_pcall见 protected_function.hpp并针对const char*、std::string、std::exception等类型做分类捕获把 C 函数里抛出的异常转成 Lua 错误再传回结果。10.3 exception_handler捕获从 C 函数泄漏的异常sol2 还提供sol::exception_handler_function当绑定到 Lua 的 C 函数抛出异常并冒泡出来时被注册的处理器会被调用且必须向栈上压入 1 个元素通常是一个字符串用于lua_error。官方示例 exception_handler.cppint my_exception_handler(lua_State* L, sol::optionalconst std::exception maybe_exception, sol::string_view description) { // 打印异常信息…… // 必须压入 1 个元素作为 Lua 侧的错误对象 return sol::stack::push(L, description); } void will_throw() { throw std::runtime_error(oh no not an exception!!!); } int main() { sol::state lua; lua.open_libraries(sol::lib::base); lua.set_exception_handler(my_exception_handler); lua.set_function(will_throw, will_throw); sol::protected_function_result pfr lua.safe_script( will_throw(), sol::script_pass_on_error); sol_c_assert(!pfr.valid()); sol::error err pfr; std::cout err.what() std::endl; }十一、受保护函数不是万能加载失败的边界情况sol::protected_function只能保护函数调用环节。如果问题出在文件加载本身——例如脚本文件路径错误、文件内容无法解析、执行前的加载步骤就失败——那么单纯包一层protected_function救不了你。正确的兜底姿势是注册自己的 panic 处理器捕获到达 panic 的错误或遵循第八节的捕获 崩溃策略或在极端场景下放弃 sol2 的内置保护直接绑定一个裸lua_CFunction完全自己掌控错误流。十二、表迭代的注意点对 Lua 表做迭代时有两个常见陷阱表上可能挂着其他杂物非数字键、__pairs等导致for-each风格的循环或 sol 的for_each在处理数字段时行为奇怪。此时应改用显式的数值循环for i 1, #t来遍历数字段。迭代本身没有任何定义好的顺序。依赖顺序的代码必须显式处理排序或改用数组语义官方在 table.rst 中对此有专门说明。十三、本指南相关文件索引主题仓库路径错误处理官方文档errors.rst异常专题文档exceptions.rst配置与安全宏文档safety.rststate 自动处理器说明api/state.rst内置错误处理器实现state_handling.hppsafe/unsafe 脚本实现state_view.hpp受保护函数实现protected_function.hpp异常传播宏解析lua_version.hpp官方错误处理示例script_error_handling.cppsafe_script 示例state_script_safe.cpppanic 函数示例my_panic.cpp异常处理器示例exception_handler.cppPiccolo 引擎集成点lua_component.cpp总结sol2 的错误处理体系可以概括为三条主线——脚本层用safe_script 内置/自定义错误回调兜底调用层用sol::protected_function检查每次调用的成败VM 层用 panic 处理器 异常传播宏控制崩溃边界。而编译/链接期的各类陷阱绝大多数都能通过正确设置 C 标准、SOL_USING_CXX_LUA、SOL_EXCEPTIONS_SAFE_PROPAGATION及拆分 usertype 等针对性手段化解。建议在接入 Piccolo 引擎的 Lua 组件时把m_lua_state.script(m_lua_script)换成带错误检查的safe_script变体让每帧脚本执行的结果可观测、可诊断。赞分享游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载相关推荐sol2 错误处理与异常排查完全指南脚本安全执行、编译/链接错误、panic 与崩溃防护sol2 错误处理与异常排查完全指南脚本安全执行、编译/链接错误、panic 与崩溃防护 导读 sol2sol v3.0是 C 与 Lua 之间的高性开发工具ta-lib-python异常处理最佳实践从编译到运行时错误ta lib python异常处理最佳实践从编译到运行时错误 一、编译安装阶段异常处理 1.1 平台支持性检查 ta lib python在编译前会进行平台兼金融科技数据分析Hyperf Exception Handler 实战指南异常处理链、传播控制、Whoops 集成与错误监听器Hyperf Exception Handler 实战指南异常处理链、传播控制、Whoops 集成与错误监听器 本文以 Hyperf 协程框架内置的 hype后端微服务上一篇Input Leap 完整指南开源KVM软件免费实现一套键鼠控制多台电脑下一篇docker-selenium 发布日志深度解读Edge 128 镜像的版本矩阵与 Tag 命名机制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考