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 ...
随机推荐
- 使用-MM生成include指令和依赖生成(make include directive and dependency generation with -MM)
I want a build rule to be triggered by an include directive if the target of the include is out of d ...
- [HTML5]块和内联元素的嵌套
块元素可以包含块或内联元素,但是内联元素只能包含其他内联元素. <!-- 无效代码! :-( --> <strong> <p>你不应该把p元素放在 "st ...
- Java Socket网络编程的经典例子(转)
事实上网络编程简单的理解就是两台计算机相互通讯数据而已,对于程序员而言,去掌握一种编程接口并使用一种编程模型相对就会显得简单的多了,Java SDK提供一些相对简单的Api来完成这些工作.Socket ...
- Win32API界面库 - Project wheels 工程基础部分完成
离上次发博文过去了好久,先是要忙一个机器人的项目,然后就是部门的事情和考试周复习,然后就到了考试周,趁着复习的间隙,拾起了寒假时候抄的界面库,修掉了从前的bug. bug1 控件显示问题 当初抄这个库 ...
- 创建线程方式-NSOperation
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
- delphi控件属性和事件
常用[属性] Action:该属性是与组件关联的行为,允许应用程序集中响应用户命令 Anchors:与组件连接的窗体的位置点 Align:确定组件的对齐方式 AutoSize:确定组件是否自动 ...
- JavaScript笔记杂谈篇(啥都有)
二维码缩放比例以43PX的倍数缩放最为标准. NuGet相关管理http://www.cnblogs.com/dudu/archive/2011/07/15/nuget.html 学习笔记: http ...
- Zepto源码
// Zepto.js // (c) 2010-2016 Thomas Fuchs // Zepto.js may be freely distributed under the MIT licens ...
- wget cooikes 下载
2.下来用wget带cookie的命令下载,命令如下: wget -c –load-cookies=cookies.txt ”下载地址” -O “文件名” & [文件名处自己命名 ...
- 动态规划 - 最长递增子序列(LIS)
最长递增子序列是动态规划中经典的问题,详细如下: 在一个已知的序列{a1,a2,...,an}中,取出若干数组组成新的序列{ai1,ai2,...,aim},其中下标i1,i2,...,im保持递增, ...