unreal3脚本stacktrace的问题
在unrealscript里获取调用栈,有下面两函数:
/**
* Dumps the current script function stack to the log file, useful
* for debugging.
*/
native static final function ScriptTrace(); /**
* Gets the current script function stack back so you can log it to a specific log location (e.g. AILog).
*/
native static final function String GetScriptTrace();
这两个是Object上的静态函数,也就相当于全局函数了
最终都会调用到c++里的函数:
/**
* This will return the StackTrace of the current callstack from .uc land
**/
inline FString FFrame::GetStackTrace() const
{
FString Retval; // travel down the stack recording the frames
TArray<const FFrame*> FrameStack;
const FFrame* CurrFrame = this;
while( CurrFrame != NULL )
{
FrameStack.AddItem(CurrFrame);
CurrFrame = CurrFrame->PreviousFrame;
} // and then dump them to a string
Retval += FString( TEXT("Script call stack:\n") );
for( INT idx = FrameStack.Num() - ; idx >= ; idx-- )
{
const FFrame& f = *FrameStack(idx);
Retval += FString::Printf( TEXT("\t%s %d\n"), *f.Node->GetFullName(), f.Node->Line );
} return Retval;
}
这个f.Node->Line也就是行号,原来并没有,是我加上去的。但其实也没有太大作用。
因为对于调用栈回溯,最重要的是知道每一层函数当前执行到哪一行了,而这里的Line只是函数定义块首句所在那一行。
如果A调用了B,且在A的函数体内,有多个地方调用B,那么对于一个没有实际执行行号的stacktrace,就不知道当前A是执行到哪一句才进入B的。
要实现上述功能,只记录一个函数定义首行肯定是不够的,必须建立字节码和源码行数的映射表,这个映射表可能很大,在编译release模式时可以通过开关去除。(比如lua就是这样做的)
为什么unrealscript里没做类似的事呢,难道他们内部人员开发中不需要用到?
我觉得这可能与unrealscript的设计理念有关,因为根据文档所说,unrealscript是没有异常机制的:
- UnrealScript doesn't support generics, annotations or exception handling. Things like NullPointerException or IndexOutOfBoundsException are handled gracefully in UnrealScript by just returning null values after writing a warning message to the log file. Typecasting object references is safe and also works for empty references.
以及:
The idea behind using return types in functions (or methods if you will) is to make sure it cannot return any other thing, and if it does, cast an Exception...
You can't return anything from a function with no return type, nor can you return anything of the wrong type (except in cases where there is an automatic cast) - the compiler will give an error. That's much better than throwing an exception.
以及:
having an exception handler would also add that the game halts on non-trapped exceptions. You really wouldn't want your game halting and dying on any error. Besides, there's only a handful of possible error conditions that can happen at runtime, anyway.
总结来说,就是unrealscript本身是一种强类型的编译型脚本,很多问题在编译期就可以发现了,而一旦进入运行期,遇到错误,首发目标是对它进行兼容处理,而非抛出异常。比如很典型的【Access None】,这在c++里就是访问空指针这样严重错误了,但unrealscript并不抛出异常,只是简单的打一个warn到log,然后默默的执行下一句代码。。
这样的好处是,函数的大部份语句得以执行,不会因某一句的错误,而中断、改变了流程。
既然是这样,那stack trace确实没多大的用处了,因为它的作用就是出现异常的时候,去了解出错时的调用链。
几个附加备忘:
1、关于函数定义的行号,实际上并没有跟源文件中函数声明那一行严格匹配,这让我曾一度很困惑,但是通过观察调试脚本编译流程,知道了原因所在:函数的行号,实际是它体内第一句“非声明型语句”所在的那一行。也就是头部所有【local classXXX varYYY】这样的语句都不算数,直到碰到【x=y】这样的才算第一句代码。具体的实现在UnSrcCom.cpp里:
UBOOL FScriptCompiler::CompileStatement()
{
UBOOL NeedSemicolon = ; // Get a token and compile it.
FToken Token;
if( !GetToken(Token,NULL,) )
{
// End of file.
return ;
}
else if( !CompileDeclaration( Token, NeedSemicolon ) )
{
if( Pass == PASS_Parse )
{
// Skip this and subsequent commands so we can hit them on next pass.
if( NestLevel < )
{
ScriptErrorf(SCEL_Unknown/*SCEL_Formatting*/, TEXT("Unexpected '%s'"), Token.Identifier );
}
UngetToken(Token);
PopNest( TopNest->NestType, NestTypeName(TopNest->NestType) );
SkipStatements( , NestTypeName(TopNest->NestType) );
NeedSemicolon = ; ……
在CompileDeclaration失败后(也就是不再是一条声明语句时),才转入PopNest,也就是终结上一个AST节点的处理,也是在那里才对Function节点的Line属性赋值:
void FScriptCompiler::PopNest( ENestType NestType, const TCHAR* Descr )
{
……// Pass-specific handling.
if( Pass == PASS_Parse )
{
// Remember code position.
if( NestType==NEST_State || NestType==NEST_Function )
{
TopNode->TextPos = InputPos;
TopNode->Line = InputLine;
……
而走到这一步之前,已经查看了好多Token,也就是往前读过了好多字符,包括换行,所以这里的InputLine已经不是函数声明所在那一行,而是发现当前AST是非声明语句的那一行了。
以上是在编译【完全的函数定义】时的情形,对于一句话式的声明,如:
native static final function ScriptTrace();
这种又当如何呢?在这种情形下,结尾的那个分号,就可以归约一句函数声明,从而在CompileDeclaration->CompileFunctionDeclaration里也调用到PopNest,进而对InputLine赋值,这时InputLine就是当前行,所以这种函数的行号是准确匹配的。
2、在vc里调试时,为了方便观察特定情况,需要设条件断点,通常可以对名字类的字符串进行比较来达到目的。而Unreal3里表示字符串的FString和FName都是很trick的结构,无法直接拿它们跟literals比较(主要是断点条件表达式并不支持c++重载这种高级语法),需要拿到这两个结构里真正的数据地址,才能进行比较:
FString:wcscmp(L"Engine.GameViewportClient",Value.AllocatorInstance.Data)==0,注意Unreal3用的是Unicode字符串
FName:strcmp("GameViewportClient",((FNameEntry**)InClass->Name.Names.AllocatorInstance.Data)[InClass->Name.Index]->AnsiName)==0
unreal3脚本stacktrace的问题的更多相关文章
- MonoBehaviour Lifecycle(生命周期/脚本执行顺序)
脚本执行顺序 前言 搭建一个示例来验证Unity脚本的执行顺序,大概测试以下部分: 物理方面(Physics) 渲染(Scene rendering) 输入事件(InputEvent) 流程图 Uni ...
- unreal3的viewport和client
名字里带viewport/client的类不少,以及相关的类: FViewportFrame.FViewport FViewportClient/UScriptViewportClient/UGame ...
- unreal3之FName及潜在bug
FName是unreal3里对字符串高效处理的一种机制 基本原理就是把字符串hash存起来,然后每个字符串就只需要用一个数组索引值来表示了 FName的属性: NAME_INDEX Index; IN ...
- JS魔法堂:获取当前脚本文件的绝对路径
一.前言 当写模块加载器时,获取当前脚本文件的绝对路径作为基础路径是必不可少的一步,下面我们一起来探讨一下这个问题吧! 二.各大浏览器的实现方式 [a]. Chrome和FF 超简单的一句足矣! va ...
- Selenium2学习-005-WebUI自动化实战实例-003-三种浏览器(Chrome、Firefox、IE)启动脚本源代码
此文主要通过 三种浏览器(Chrome.Firefox.IE)启动脚本 功能,进行 Selenium2 三种浏览器启动方法的实战实例讲解.文中所附源代码于 2015-01-18 20:33 亲测通过, ...
- 使用Groovy构建自己的脚本环境
场景 在进行Web服务端开发的时候,发布前通常需要测试一遍.对于一个大一点的项目,最好的办法是写个自动化测试程序. 以Groovy为例,写测试代码之前通常的有如下几个操作 引用相关的类库 import ...
- C#应用程序隐藏调用bat脚本
做c#应用程序有些调用windows自带的bat脚本会比较方便 Process proc; proc = null; try { string targetDir = GetParentUrl() + ...
- appium---第一个脚本--启动一个已存在的app
1.可以使用android-sdk中的aapt工具 ①.选择一个版本的build_tools,加入path环境变量中 ②.验证aapt环境是否正常 3.下载你要测试的包,放入某一地址中(随意):aap ...
- Gradle 命令之 --stacktrace , --info , --debug 用法
FAQ: Android studio 出现错误Run with --stacktrace option to get the stack trace. Run with --info or --de ...
随机推荐
- [platform]linux platform device/driver(三)--Platform Device和Platform_driver注册过程之代码对比
转自:http://blog.csdn.net/thl789/article/details/6723350 Linux 2.6的设备驱动模型中,所有的device都是通过Bus相连.device_r ...
- 黄聪: PHP WkHtmlToPdf/WkHtmlToImage 将网页直接转换成pdf和图片
function convert($type='pdf') { $filename=time(); $url=$this->input->get("url"); if( ...
- (C#) 基本概念一览表
A abstract class An abstract class is a class that must be inherited and have the methods overridden ...
- nbu恢复 oracle10g rac asm 到单实例asm(恢复某个表空间)
一 检验条件 二 准备工作 1 创建相关目录 mkdir -p /oracle/admin/orcl/adumpmkdir -p /oracle/admin/orcl/bdumpmkdir -p /o ...
- Scrum学习总结
在学习的过程中,记录一些重要的东东,一为认真学习,作下归纳总结:二为以后查阅,好记性不如烂笔头!如果大家认为太简单,欢迎喷喷^_^ Scrum:一种迭代式增量软件开发过程,通常用于敏捷软件开发.Scr ...
- js按Enter键提交表单
function exprint(e){ /* var keycode = event.keyCode; if (keycode == "13"){ fm.UserCode.foc ...
- 关于new/delete、malloc/free的内存泄漏检测
情况一 new/delete 内存泄漏 1.在MFC中可以每一个cpp文件的头部添加以下一段宏来检测new申请而没用free释放的内存泄漏 #ifdef _DEBUG #define new DEBU ...
- Linux_07------Linux的用户和用户组管理
段 * 用户名:密码占位符:用户编号:用户组编号:用户注释信息:用户主目录:shell类型 * 每一行对应一个用户 * * /etc/shadow 存储用户密码 * 与passwd配置文件一一对应, ...
- python类——黑板客老师课程学习
1.基本语法 class class_name(base_class): base_class是它继承的父类 class_var def methods(self,args): statements ...
- Error -27780: [GENERAL_MSG_CAT_SSL_ERROR]connect to host "124.202.213.70" failed: [10054] Connection reset by peer [MsgId: MERR-27780]
解决方案一: 备注: 此方案如果请求响应时间太长,勾选"WinInet replay instead of Sockets(Windows only)"将会导致如下错误: