代码改变世界

[置顶]《游戏引擎架构》信息总汇

2014-02-18 10:23 by Milo Yip, 阅读(28082) 阅读, 推荐(18) 推荐, 收藏, 编辑
摘要:豆瓣当当亚马逊China-pub京东中国图书网淘宝PDF样章试读微盘下载微云下载中英词汇索引表本页内容基本信息作者:Jason Gregory (杰森.格雷戈瑞)译者:Milo Yip(叶劲峰)原书名:Game Engine Architecture出版社:电子工业出版社出版时间:2014-1-1开本:16页数:800字数:1093400ISBN:9787121222887内容简介《游戏引擎架构》同时涵盖游戏引擎软件开发的理论及实践,并对多方面的题目进行探讨。本书讨论到的概念及技巧实际应用于现实中的游戏工作室,如艺电及顽皮狗。虽然书中采用的例子通常依据一些专门的技术,但是讨论范围远超于某个引擎 阅读全文

[置顶]本博方针

2010-06-14 21:34 by Milo Yip, 阅读(5852) 阅读, 推荐(6) 推荐, 收藏, 编辑
摘要:为希望本博能保持风格、质量,本文整理一直以來我想到的博客方针。 选题通用、时效长久的知识经验分享(杂谈除外) 以游戏开发为主(如图形、物理、人工智能、游戏编程、引擎架构等),但也涉猎一般计算机科学知识(如算法) 避免讨论最新热门技术,维持博文的原创性、独特性不转载、不翻译(自己的文章除外) 不撰书评(书评撰在豆瓣)可谈哲学问题,不谈宗教问题(如Windows好还是Linux好)撰文尽量客观,以理论支持观点,提供参考和实验数据(如果纯属猜想会标明) 尽量把题目写得浅白,不需读者懂得大量背景知识尽量使用普及的软件、编程语言等,让读者可以自行尝试、实验希望维持每周一文,繁忙时也至少两周一文(包括转载 阅读全文

RapidJSON v1.1.0 发布简介

2016-08-28 10:26 by Milo Yip, 阅读(7377) 阅读, 推荐(11) 推荐, 收藏, 编辑

时隔 15.6 个月,终于发布了一个新版本 v1.1.0

新版本除了包含了这些日子收集到的无数的小改进及 bug fixes,也有一些新功能。本文尝试从使用者的角度,简单介绍一下这些功能和沿由。

Photo by Ian Schneider

JSON Pointer

也许 RapidJSON 一直最为人垢病的地方,是它奇怪的 API 设计。例如,对 DOM 加添数据要给于 allocator 参数:

#include "rapidjson/document.h"

using namespace rapidjson;

// ...

Document d(kObjectType);

Value a(kArrayType);
for (int i = 1; i <= 4; i++)
    a.PushBack(i, d.GetAllocator());

d.AddMember("a", a, d.GetAllocator());

// { a : [1, 2, 3, 4] }

这是由于 RapidJSON 的 DOM 使用局部的分配器,以避免全局分配器的问题。而为了节省内存,每个 Value 不会存储分配器的指针,所以必须从外部提供。

此设计也导致另一种问题。我们看看一个例子,使用 DOM API 访问以下这个 JSON:

{
    "widget": {
        "window": {
            "title": "Sample Konfabulator Widget"
        }
    }
}

要访问 title,最直觉想到的应该是这样:

Document d;
d.Parse(json);
std::cout << d["widget"]["window"]["title"].GetString();

如果 widgetwindowtitle 不存在呢?以标准库 std::map::operator[] 的做法来说,当找不到键,它会自动加入一个键值对,并返回该值(所以 map::operator[] 必须是 non-const 函数)。然而,RapidJSON 创建值的时候需要 allocator,所以不可能自动加入键值对。因此,RapidJSON 规定以 operator[] 访问时,必须确保键存在(找不到时直接断言失败)。若不能确保,应先用 HasMember() 判断,或更好的是使用 FindMember(),因为它可以告之键是否存在的同时,能通过迭代器取得该值。可是,使用 FindMember() 去访问多层对象,代码非常冗长:

Value::ConstMemberIterator itr1 = d.FindMember("widget");
if (itr1 != d.MemberEnd()) {
    const Value& widget = itr1->value;
    if (widget.IsObject()) {
        Value::ConstMemberIterator itr2 = widget.FindMember("window");
        if (itr2 != widget.MemberEnd()) {
            const Value& window = itr2->value;
            if (window.IsObject()) {
                Value::ConstMemberIterator itr3 = window.FindMember("title");
                if (itr3 != window.MemberEnd()) {
                    const Value& title = itr3->value;
                    if (title.IsString()) {
                        std::cout << title.GetString();
                    }
                }
            }
        }
    }
}

这坨代码也许是最快最直接的方式。但一般业务代码写成这样,可读性太低,也容易出错。

大家都可以写一些辅助函数来解决这个问题。而我选择了实现 RFC6901 ── JSON Pointer。先看看使用后的结果:

#include "rapidjson/pointer.h"

// ...

if (const Value* title = GetValueByPointer(d, "/widget/window/title")) {
    if (title->IsString()) {
        std::cout << title->GetString();
    }
}

这个版本简单得多吧,"/widget/window/title" 是一个 JSON Pointer 的字符串形式,然后 GetValueByPointer()d 上解引用,如果失败就返回空指针。

在逻辑上是和上面的冗长版本是一模一样的,只是增加了一些解析 JSON Pointer 的运行时间及空间成本。对大多数人来说,应该更会接受这个版本。

有时候,业务逻辑还会是这样的:「如果键不存在,就使用缺省值。」RapidJSON 的 JSON Pointer 也提供此功能:

Value& title = GetValueByPointerWithDefault(
    d, "/widget/window/title", "untitled");

当解引用失败时,它会创建整个路径,并把预设值复制成新值,并返回该值。由于它总能返回一个值,此函数的返回类型为引用而不是指针。

在此也简单介绍一下 JSON Pointer 的语法。它以 '/' 分隔多个 token,而每个 token 可以是 JSON object 的键,也可以是 JSON array 的下标。还有一种特殊 token 是负号 -,它可以指 JSON array 最后元素的下一个元素。使用这种特性能实现 PushBack() 的效果:

Document d;
CreateValueByPointer(d, "/a").SetArray();

for (int i = 1; i <= 4; i++)
    SetValueByPointer(d, "/a/-", i);

// { a : [1, 2, 3, 4] }

使用 JSON Pointer 的另一优点在于,它本身也是一个字符串,可以放置在 JSON 或其他文本格式之中。那么,我们便有一个标准方式去引用 JSON 中的值。

希望 JSON Pointer 能减轻使用者的负担,同时也提供一种数据驱动的弹性。新功能 JSON Pointer 简单介绍至此,更多信息可参考 RapidJSON 使用手册:Pointer

JSON Schema

上面我们也谈到一个问题,JSON 里的组织方式、类型可能和预期的不同,我们可能要写很多代码去校验一个 JSON 的格式是否乎合预期。特别是后台服务器可能接收到不正常的JSON,甚至是恶意编写的 JSON 以图攻击。

在 XML 的世界中,可使用 XML DTD 或 XML Schema 去描述 XML 的结构。在 JSON 的世界中,已经有相关草案,称为 JSON Schema

RapidJSON 实现了 JSON Schema v4 draft,并正式纳入了 v1.1.0。先看看用法:

#include "rapidjson/schema.h"
// ...
Document sd;
if (!sd.Parse(schemaJson).HasParseError()) {
    // 此 schema 不是合法的 JSON
}
SchemaDocument schema(sd); // 把一个 Document 编译至 SchemaDocument
// 之后不再需要 sd
Document d;
if (!d.Parse(inputJson).HasParseError()) {
    // 输入不是一个合法的 JSON
}
SchemaValidator validator(schema);
if (!d.Accept(validator)) {
    // 输入的 JSON 不合乎 schema
}

以我所知,现时所有 JSON Schema 实现都是校验一个 DOM 是否合乎 Schema。RapidJSON 做了一个创新的尝试,以事件流(SAX 风格)的方式去做校验。上面的例子利用 Document::Accept() 产生事件流,然后送交 SchemaValidator 校验。也许读者会问:「这也是在校验一个 DOM 是否合乎 Schema,有什么特别吗?」

这实际意味着,RapidJSON 的 JSON Schema 校验器除了可以校验 DOM,也可以校验更底层的 SAX。例如,我们可以用 SAX 解析 JSON 时,同时进行 JSON Schema 校验。如果中途不合乎 JSON Schema,就能直接中止解析。

SchemaValidator validator(schema);
Reader reader;
if (!reader.Parse(stream, validator)) {
    if (!validator.IsValid()) {
        // 输入的 JSON 不合乎 schema
    }
}

也可以同时把事件转发至一个自定义 handler:

MyHandler handler;
GenericSchemaValidator<SchemaDocument, MyHandler> validator(schema, handler);
Reader reader;
if (!reader.Parse(stream, validator)) {
    if (!validator.IsValid()) {
        // 输入的 JSON 不合乎 schema
    }
}

由于 DOM 解析 JSON 时,底层也是使用 SAX,所以也可以同时做 Schema 校验。其实除了解析,在生成时也可以进行校验,以确保输出的 JSON 也是乎合 Schema 的。这些用法都可参考 RapidJSON 使用手册:Schema。要学习 JSON Schema 的写法,笔者推荐 Understanding JSON Schema 这个英文网站。

C++11 范围 for 循环

此版本还加入了 ArrayObject 辅助类型(包裹类),可分别通过 Value::GetArray()Value::GetObject() 获取。这两个辅助类型提供该 JSON 类型专门的接口,例如 Array::PushBack()Object::AddMember() 等。更重要的是,为了令 C++11 用户使用得更顺手,它们可做范围 for 循环(range-based for loop):

// C++03
for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
    printf("%d ", itr->GetInt());

// C++11
for (auto& v : a.GetArray())
    printf("%d ", v.GetInt());

// C++03
for (Value::ConstMemberIterator itr = document.MemberBegin();
    itr != document.MemberEnd(); ++itr)
{
    printf("Type of member %s is %s\n",
        itr->name.GetString(), kTypeNames[itr->value.GetType()]);
}

// C++11
for (auto& m : document.GetObject())
    printf("Type of member %s is %s\n",
        m.name.GetString(), kTypeNames[m.value.GetType()]);

其他相关详情可参阅 RapidJSON 使用手册:教程

结语

这个 RapidJSON 版本对我而言是一个挑战。

JSON Schema 实际上也需要 JSON Pointer,所以 JSON Pointer 可算是一举两得的新功能。但实现 JSON Schema 时有两个难点。一个是 JSON Schema 需要正则引擎,在 C++11 下能直接使用 std::regex;而为了 C++03,我还实现了一个 500 行代码的 Thompson NFA 正则引擎。另一个难点在于,事件流的校验不容易实现 allOfanyOfoneOfnot 等关键字,需要多个校验器同时检验事件流。

新功能 JSON Schema 和 JSON Pointer 都是附加功能,完全不影响 v1.0.x 的 API。

除新功能外,此版本含有一个重要的内存优化。在 x86-64 架构下,64 位指针只使用到 48 位,我重新设计了 Value 的排布,使每个值的内存开销从 24 字节缩减至 16 字节。虽然存储指针时会有时间开销,但因大量缩减内存,更好的缓存一致性应该可以厘补损失,甚至能进一步提升整体性能。

屈指一算,RapidJSON 已快近 5 个年头了,最近一年我转部门后,更少机会在工作上使用 RapidJSON,所以我可能较少机会发现问题和新需求。虽然是这样,我仍然会继续维护这个项目,也要靠大家去发现问题和新需求,希望能得到大家的意见。

P.S. 可能大家会关心性能,我会尽快更新 nativejson-benchmark

RapidJSON 代码剖析(四):优化 Grisu

2015-06-30 13:03 by Milo Yip, 阅读(10876) 阅读, 推荐(18) 推荐, 收藏, 编辑
摘要:我曾经在知乎的一个[答案](http://www.zhihu.com/question/22498967/answer/29472607)里谈及到 V8 引擎里实现了 Grisu 算法,我先引用该文的内容简单介绍 Grisu。然后,再谈及 RapidJSON 对它做了的几个底层优化。 阅读全文

RapidJSON 代码剖析(三):Unicode 的编码与解码

2015-06-03 17:34 by Milo Yip, 阅读(13451) 阅读, 推荐(8) 推荐, 收藏, 编辑
摘要:RapidJSON 希望尽量支持各种常用 UTF 编码,用[四百多行代码][encodings.h]实现了 5 种 Unicode 编码器/解码器,另外加上 ASCII 编码。本文会简单介绍它的实现方式。 阅读全文

RapidJSON 代码剖析(二):使用 SSE4.2 优化字符串扫描

2015-05-20 14:27 by Milo Yip, 阅读(7720) 阅读, 推荐(5) 推荐, 收藏, 编辑
摘要:现在的 CPU 都提供了[单指令流多数据流][单指令流多数据流](single instruction multiple data, SIMD)指令集。最常见的是用于大量的浮点数计算,但其实也可以用在文字处理方面。 其中,SSE4.2 包含了一些专为字符串而设的指令。我们通过使用这些指令,可以大幅提升某些 JSON 解析的性能。 阅读全文

RapidJSON 代码剖析(一):混合任意类型的堆栈

2015-05-14 09:53 by Milo Yip, 阅读(9451) 阅读, 推荐(10) 推荐, 收藏, 编辑
摘要:大家好,这个专栏会分析 RapidJSON 中一些有趣的 C++ 代码,希望对读者有所裨益。 阅读全文

《游戏引擎架构》中英词汇索引表

2014-02-23 17:26 by Milo Yip, 阅读(8708) 阅读, 推荐(2) 推荐, 收藏, 编辑
摘要:简介此词汇索引表源自《游戏引擎架构》的中英索引,支持搜寻及排序,以方便读者查阅。遇到游戏相关的术语也可利用本表查找其中英翻译。欢迎提供意见反馈。中英双语索引表英文中文页数2D angular acceleration二维角加速率5802D angular dynamics二维旋转动力学5802D angular speed二维角速率5802D angular velocity二维角速度5802D orientation二维定向5803D angular dynamics三维旋转动力学5833D angular momentum三维角动量5843D angular velocity三维角速度58 阅读全文

面试题:检测点是否在扇形之内

2013-04-19 10:22 by Milo Yip, 阅读(33849) 阅读, 推荐(12) 推荐, 收藏, 编辑
摘要:前几天,同事在报告中提及检测角色是否在扇形攻击范围的方法。我觉得该方法的性能不是太好,提出另一个颇为直接的方法。此问题在游戏中十分常见,只涉及简单的数学,却又可以看出实现者是否细心,所以我觉得可当作一道简单的面试题。问题在微博发表后得到不少回应,故撰文提供一些解答。问题定义:在二维中,检测点\mathbf{p}是否在扇形(circular sector)内,设扇形的顶点为\mathbf{c},半径为r,从\mathbf{\hat{u}}方向两边展开\theta角度。当中 \mathbf{p},\mathbf{c},\mathbf{\hat{u}} 以直角坐标(cartesian coordin 阅读全文

爱丽丝的发丝──《爱丽丝惊魂记:疯狂再临》制作点滴

2011-06-14 16:27 by Milo Yip, 阅读(102610) 阅读, 推荐(109) 推荐, 收藏, 编辑
摘要:今天(2011年6月14日)是《爱丽丝惊魂记:疯狂再临 (Alice: Madness Returns) Xbox360/PlayStation3/PC》(下简称《爱》)正式发售日,身为其开发程序员之一,特撰此文以作纪念。简介《爱》(图1a)是一款由上海独立游戏工作室麻辣马(Spicy Horse)... 阅读全文

C++强大背后

2010-09-17 00:56 by Milo Yip, 阅读(99956) 阅读, 推荐(179) 推荐, 收藏, 编辑
摘要:在31年前(1979年),一名刚获得博士学位的研究员,为了开发一个软件项目发明了一门新编程语言,该研究员名为Bjarne Stroustrup,该门语言则命名为——C with classes,四年后改称为C++。C++是一门通用编程语言,支持多种编程范式,包括过程式、面向对象(object-oriented programming, OP)、泛型(generic programming, GP)... 阅读全文

手机分配短讯id的面试题目(分析解答篇)

2010-09-03 01:37 by Milo Yip, 阅读(13034) 阅读, 推荐(25) 推荐, 收藏, 编辑
摘要:看过上回《厘清需求篇》,读者想到多少个解呢?本篇首先谈及一些基本分析,之后会按两种API设计(纯函数API和含状态的API),分别描述多个解。虽然面试时或许不能进行实际测试,但本文还是给出PC上的效能测试结果。最后分析比较各解之优劣作为总结。 问题分析原来的问题是要从一个无序ids数组里分配一个id。我们可以用数学方式去更清楚地说明这个问题。设m = 256 为所有id的个数,集合U = \lef... 阅读全文

手机分配短讯id的面试题目(厘清需求篇)

2010-08-31 01:42 by Milo Yip, 阅读(13944) 阅读, 推荐(12) 推荐, 收藏, 编辑
摘要:前阵子,笔者在TopLanguage论坛里参与讨论了一个不错的面试题目,在此和大家分享,也当作个人的讨论总结。本文列出该问题,并模拟应试者向面试官的对话,以厘清问题需求。题目原文事缘Dbger发起的帖子中,liuxinyu举了一个面试题目,原文如下:有个老的手机短信程序,由于当时的手机CPU,内存都很烂。所以这个短信程序只能记住256条短信,多了就删了。 每个短信有个唯一的ID,在0到255之间。... 阅读全文

C++/C#/F#/Java/JS/Lua/Python/Ruby渲染比试

2010-07-07 00:20 by Milo Yip, 阅读(66311) 阅读, 推荐(54) 推荐, 收藏, 编辑
摘要:前篇博文把一个C++全局光照渲染器移植至C#,比较C++和C#之性能。……本人陆续移植了C++代码至Java、JavaScript、Lua、Python和Ruby,赵姐夫亦尝试了F#。本文提供源代码、测试结果、简单分析、以及个人体会。 阅读全文

C# vs C++ 全局照明渲染性能比试

2010-06-23 09:48 by Milo Yip, 阅读(32499) 阅读, 推荐(58) 推荐, 收藏, 编辑
摘要:最近有多篇讨论程序语言趋势的博文,其中谈及到C#的性能问题。本人之前未做过相关测试,自己的回覆流于理论猜测,所以花了点时间做个简单实验,比较C#和C++的性能。 阅读全文

用JavaScript玩转游戏物理(一)运动学模拟与粒子系统

2010-06-14 16:19 by Milo Yip, 阅读(51943) 阅读, 推荐(138) 推荐, 收藏, 编辑
摘要:系列简介也许,三百年前的艾萨克·牛顿爵士(Sir Issac Newton, 1643-1727)并没幻想过,物理学广泛地应用在今天许多游戏、动画中。为什么在这些应用中要使用物理学?笔者认为,自我们出生以来,一直感受着物理世界的规律,意识到物体在这世界是如何"正常移动",例如射球时球为抛物线(自旋的球可能会做成弧线球) 、石子系在一根线的末端会以固定频率摆动等等。要让游戏或动画中的物体有真实感,其... 阅读全文

回应CSDN肖舸《做程序,要“专注”和“客观”》,实验比较各离散采样算法

2010-05-27 23:57 by Milo Yip, 阅读(25750) 阅读, 推荐(101) 推荐, 收藏, 编辑
摘要:自从肖舸在其CSDN博客上说“拒绝回答博客园等网站网友的问题”,实质上不单是拒绝回答,而且还删去包括一些网友及本人对于纯粹技术探讨的评论。当然每位博主都有自由这么做,但个人认为这对于社区的交流发展有负面影响。为了探讨这个技术问题,本人唯有把回应发表于博客园内。本文会阐述之前的论点,评论肖舸的实现,并进行了兩个实验比较不同算法、实现的优劣之处。 之前的“交流&rd... 阅读全文

《编程之美:分层遍历二叉树》的另外两个实现

2010-05-12 00:10 by Milo Yip, 阅读(30202) 阅读, 推荐(24) 推荐, 收藏, 编辑
摘要:之前重温本书写书评时,也尝试找寻更好的编程解法。今天把另一个问题的实现和大家分享。问题定义给定一棵二叉树,要求按分层遍历该二叉树,即从上到下按层次访问该二叉树(每一层将单独输出一行),每一层要求访问的顺序为从左到右,并将节点依次编号。下面是一个例子: 输出:节点的定义:书上的解法书上举出两个解法。第一个解法是用递归方式,搜寻并打印某一层的节点,再打印下一层的节点。这方法简单但时间效率不高(但不需要... 阅读全文

在博客里轻松使用LaTeX数学公式

2010-04-26 01:32 by Milo Yip, 阅读(23432) 阅读, 推荐(30) 推荐, 收藏, 编辑
摘要:笔者最近的博文有不少数学相关内容,发现利用一些网上服务、jQuery和CSS,可以更轻松地在博客里使用\LaTeX语法排版方程式。\LaTeX是基于\TeX的排版系统。而\TeX就是美国著明计算机教授高德纳(Donald E. Knuth),为了编写他的巨著《计算机程序设计艺术(The Art of Computer Programming)》而设计的系统,对于数学公式的排版支援十分强大。线上La... 阅读全文

用JavaScript玩转游戏编程(一)掉宝类型概率

2010-04-21 14:50 by Milo Yip, 阅读(34676) 阅读, 推荐(23) 推荐, 收藏, 编辑
摘要:问题定义游戏(和一些模拟程序)经常需要使用随机数,去应付不同的游戏(或商业)逻辑。本文分析一个常见问题:有N类物件,设第i类物件的出现概率为P(X=i),如何产生这样的随机变量X?例如对概率的要求是P(X=0)=0.12P(X=1)=0.4P(X=2)=0.4P(X=3)=0.07P(X=4)=0.01输入数组<0.12, 0.4, 0.4, 0.07, 0.01>输出符合以上概率的随... 阅读全文

从头开始思考游戏的数据管理系统(二)

2010-04-13 13:10 by Milo Yip, 阅读(8388) 阅读, 推荐(8) 推荐, 收藏, 编辑
摘要:上回谈及一些游戏数据管理的初部分析,但有些思绪还没有整理好,写得颇乱。今次就直接谈笔者在几个月前设计的Mil Universe Database(MUD),从中再阐述当中的一些想法。 基本的数据流程首先,一个项目中,引擎会使用的所有数据,都放进一个完整的数据库里。现时笔者的设计一个数据库会储存成一个.mud文件。 外部文件可以汇入数据库,也可以汇出。但汇出的数据会失去一些资讯(如稍后说的依存关系)... 阅读全文
点击右上角即可分享
微信分享提示