在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的问题的更多相关文章

  1. MonoBehaviour Lifecycle(生命周期/脚本执行顺序)

    脚本执行顺序 前言 搭建一个示例来验证Unity脚本的执行顺序,大概测试以下部分: 物理方面(Physics) 渲染(Scene rendering) 输入事件(InputEvent) 流程图 Uni ...

  2. unreal3的viewport和client

    名字里带viewport/client的类不少,以及相关的类: FViewportFrame.FViewport FViewportClient/UScriptViewportClient/UGame ...

  3. unreal3之FName及潜在bug

    FName是unreal3里对字符串高效处理的一种机制 基本原理就是把字符串hash存起来,然后每个字符串就只需要用一个数组索引值来表示了 FName的属性: NAME_INDEX Index; IN ...

  4. JS魔法堂:获取当前脚本文件的绝对路径

    一.前言 当写模块加载器时,获取当前脚本文件的绝对路径作为基础路径是必不可少的一步,下面我们一起来探讨一下这个问题吧! 二.各大浏览器的实现方式 [a]. Chrome和FF 超简单的一句足矣! va ...

  5. Selenium2学习-005-WebUI自动化实战实例-003-三种浏览器(Chrome、Firefox、IE)启动脚本源代码

    此文主要通过 三种浏览器(Chrome.Firefox.IE)启动脚本 功能,进行 Selenium2 三种浏览器启动方法的实战实例讲解.文中所附源代码于 2015-01-18 20:33 亲测通过, ...

  6. 使用Groovy构建自己的脚本环境

    场景 在进行Web服务端开发的时候,发布前通常需要测试一遍.对于一个大一点的项目,最好的办法是写个自动化测试程序. 以Groovy为例,写测试代码之前通常的有如下几个操作 引用相关的类库 import ...

  7. C#应用程序隐藏调用bat脚本

    做c#应用程序有些调用windows自带的bat脚本会比较方便 Process proc; proc = null; try { string targetDir = GetParentUrl() + ...

  8. appium---第一个脚本--启动一个已存在的app

    1.可以使用android-sdk中的aapt工具 ①.选择一个版本的build_tools,加入path环境变量中 ②.验证aapt环境是否正常 3.下载你要测试的包,放入某一地址中(随意):aap ...

  9. Gradle 命令之 --stacktrace , --info , --debug 用法

    FAQ: Android studio 出现错误Run with --stacktrace option to get the stack trace. Run with --info or --de ...

随机推荐

  1. 使用air16sdk打包ipa报错

    报错如下图: google下 https://forums.adobe.com/thread/1659726 说明了一切 首先:air sdk中打包ipa 需要使用ios sdk的路径但是这个路径最后 ...

  2. 如何测试手机上的SOAP客户端

    周四晚上,服务端和客户端的两个同事因为soap接口的问题争论了起来.服务端的同事认为客户端的同事发给服务端的soap消息的xml结构有问题,少了几个xml节点,导致服务器端解析出错.而客户端的同事认为 ...

  3. Android OpenCV 图像识别

    最近打算写一个android 平台opencv 的小程序,着手查找了一下资料.网络上的资料参差不齐,有一些都比较老旧,我参考了前面的方法找到了一个简单的搭建方法,分享给大家. 0,环境的搭建: jav ...

  4. mac与php环境

    一.目录 apache目录:/etc/apache2/ mysql目录:/usr/local/mysql/ 站点目录:/Library/WebServer/Documents/ 二.mac系统给文件夹 ...

  5. 数据库 MySql

    MySql 一,概述 1,什么是数据库? 数据的仓库,如:在ATM的示例中我们创建了一个 db 目录,称其为数据库 二,下载安装 想要使用MySQL来存储并操作数据,则需要做几件事情: a. 安装My ...

  6. [CF442B] Andrey and Problem (概率dp)

    题目链接:http://codeforces.com/problemset/problem/442/B 题目大意:有n个人,第i个人出一道题的概率是pi,现在选出一个子集,使得这些人恰好出一个题的概率 ...

  7. innerText引发的错误

    因为firefox对innerText的不支持,所以以下代码在firefox里运行有错误. //重新加载饼图 ") { var gridView = document.getElementB ...

  8. django(五)

    URLs 当一个用户请求一个页面时,Django将按照顺序去匹配每一个模式,并停在第一个匹配请求的URL上. 如果你的url多个正则表达式都能匹配上咋弄?小心出错,这个是按照顺序匹配的 url(r'^ ...

  9. python中的装饰器

    一.什么是装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能 ...

  10. java使用xsd校验xml样例

    知识点:XSD文件是指XML结构定义 ( XML Schemas Definition )文件,是DTD的替代品.可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其 ...