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 ...
随机推荐
- Hadoop学习15--编程相关
1.新版本:mapreduce包 2.job设置: a.JobName便于调试 b.FileInputFormat.addInputPath 添加输入路径,可添加多个:可以是具体文件,还可以是文件夹. ...
- eclipse 代码自动提示
从Window -> preferences -> Java -> Editor -> Content assist -> Auto-Activation下,我们可以在& ...
- [IIS]IIS扫盲(五)
第二篇 IIS之FTP服务器 一.建立你的FTP站点 第一个FTP站点(即“默认FTP站点”)的设置方法和更多FTP站点的建立方法请参照前文Web服务器中相关操作执行.需要注意的是,如果你要用一个IP ...
- CSS :hover伪类选择定义和用法
伪类选择符E:hover的定义和用法: 设置元素在其鼠标悬停时的样式.E元素可以通过其他选择器进行选择,比如使用类选择符.id选择符.类型选择符等等.特别说明:IE6并非不支持此选择符,但能够支持a元 ...
- 模块化InnoSetup依赖项安装
原文在这里:http://www.codeproject.com/Articles/20868/NET-Framework-Installer-for-InnoSetup 源文件地址:http://w ...
- Oracle 增加修改删除字段与添加注释
添加字段的语法:alter table tablename add (column datatype [default value][null/not null],….); 修改字段的语法:alter ...
- AngularJs 入门系列-2 表单验证
对于日常的开发来说,最常见的开发场景就是通过表单编辑数据,这里涉及的问题就是验证问题. angularjs 内置已经支持了常见的验证方式,可以轻松实现表单验证. 1. 绑定 为了方便,我们在 $sco ...
- 第五百八十五天 how can I 坚持
时间过得真的好快啊,晚上不一会就十一点多了,稍微一堕落,时间就没了,还没来得及好好看会书.. 终于把solr拼音搜索弄好了,明天搞搞suggest. 写字,睡觉.
- 【转】Java日期计算之Joda-Time
Joda-Time提供了一组Java类包用于处理包括ISO8601标准在内的date和time.可以利用它把JDK Date和Calendar类完全替换掉,而且仍然能够提供很好的集成. http:// ...
- android 根据包名打开app程序
如: 如打开微信: 查看包名的工具app:http://pan.baidu.com/s/1kVK2ER9 效果如下: 查看包名.版本和签名的工具app:http://pan.baidu.com/s/1 ...