<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>iDom</title>
    <description>关注移动游戏 Html5 Flash UI框架</description>
    <link>https://idom.me/</link>
    <atom:link href="https://idom.me/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sat, 17 Nov 2018 14:04:25 +0000</pubDate>
    <lastBuildDate>Sat, 17 Nov 2018 14:04:25 +0000</lastBuildDate>
    <generator>Jekyll v3.7.4</generator>
    
      <item>
        <title>使用CLion开发After Effects插件</title>
        <description>&lt;p&gt;最近需要给 Adobe After Effects 开发一个插件，用于导出矢量图形到二进制文件。这篇文章不会涉及具体的插件开发细节，详细的文档可以直接参考 Adobe 官方的插件开发页面：&lt;a href=&quot;http://www.adobe.com/devnet/aftereffects.html&quot;&gt;http://www.adobe.com/devnet/aftereffects.html&lt;/a&gt;， 啃完 SDK 里的那两份 Guide 文档基本就没什么问题了。总体来说，有两种方式来开 AE 的插件：C++ 或者 ExtendScript 脚本。后者明显更容易一些，但是有一些限制，比如无法很方便的写入二进制文件，操作字节流会比较麻烦。由于我的目标是生成二进制文件，所以选择了 C++ 开发方式，并且还涉及到要运行压缩算法，C++处理上会比较有速度优势。那么问题来了，AE SDK 里的 Examples 在 Mac 平台上默认采用的是 XCode 作为开发环境[手动嫌弃]， XCode 除了打包或 Profile 时候必须用一下，平时它的文本编辑器简直不能忍。所以这篇文章的内容主要讲一下如何把 AE SDK 里的 XCode 项目转换为 CLion 项目。&lt;/p&gt;

&lt;p&gt;这里以 Examples 里的 Grabba 工程为例，文章末尾会附上转换好的完整项目压缩包。因为 CLion 用的是 CMake 编译系统，转换的过程其实就是把 XCode 项目打开，看一遍引用了哪些文件，以及需要的额外编译参数。用 CMakeLists.txt 再写一遍, 然后用 CLion 打开包含 CMakeLists.txt 的文件集即可。这是最终的  CMakeLists.txt:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.5)
project(AEM)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS &quot;-stdlib=libc++ -arch x86_64&quot;)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9)


file(GLOB_RECURSE SOURCE_FILES src/*.cpp src/*.cc src/*.c src/*.h)

list(APPEND SOURCE_FILES sdk/Util/AEGP_SuiteHandler.cpp sdk/Util/AEGP_SuiteHandler.h sdk/Util/MissingSuiteError.cpp)
include_directories(sdk/Headers sdk/Headers/SP sdk/Resources sdk/Util src)

set_source_files_properties(${SOURCE_FILES} PROPERTIES COMPILE_FLAGS &quot;${CMAKE_CXX_FLAGS} -x objective-c++ -Wno-nonportable-include-path -include /System/Library/Frameworks/Cocoa.framework/Headers/Cocoa.h&quot;)

find_library(COCOA Cocoa REQUIRED)
list(APPEND libs ${COCOA})

add_library(AEM MODULE ${SOURCE_FILES})
set_target_properties(AEM PROPERTIES
        CXX_VISIBILITY_PRESET hidden
        VISIBILITY_INLINES_HIDDEN true)
target_link_libraries(AEM ${libs})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这里大部分内容相信熟悉 CMake 的人或者参考官方文档都能轻松写出来。就是简单的查找源文件，然后设置头文件的目录。但其中有两个坑点：&lt;/p&gt;

&lt;p&gt;第一个是中间那句 &lt;code class=&quot;highlighter-rouge&quot;&gt;set_source_files_properties&lt;/code&gt; 的内容。一开始我并没有加这句，然后到处都在提示类似这样的报错信息：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sdk/Headers/AE_Effect.h:1139:11: error: unknown type name 'Fixed'
                typedef Fixed           PF_Fixed;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;说找不到 Fixed 这个类型，我查了一下这个类型是定义在 &lt;code class=&quot;highlighter-rouge&quot;&gt;/usr/include/MacTypes.h&lt;/code&gt; 文件里，但是 SDK 里的代码并没有任何地方直接 &lt;code class=&quot;highlighter-rouge&quot;&gt;#include&lt;/code&gt; 过这个头文件，那 XCode 是怎么识别出这个类型的呢？经过一番搜索后发现里编译器有个 &lt;code class=&quot;highlighter-rouge&quot;&gt;-include&lt;/code&gt; 的参数，可以直接强制预先 include 一个头文件进来。我就试了下加了句：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set_source_files_properties(${SOURCE_FILES} PROPERTIES COMPILE_FLAGS &quot;${CMAKE_CXX_FLAGS} -include /usr/include/MacTypes.h&quot;)

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;果然是不再报错找不到 &lt;code class=&quot;highlighter-rouge&quot;&gt;Fixed&lt;/code&gt; 的错误了，但是还有其他的类型找不到，说明不只这个头文件需要关联。但这么一个个找也不是办法啊。最后想起来干脆直接查看 XCode 的编译日志好了。不管工程是 XCode 还是 CMake，他们最终都是调用系统的 clang 去编译的，所以最终都会执行调用 clang 的语句，从那个语句里找缺少的参数，然后再回头添加到 CMake 里就好。这里有个详细示例如何在 XCode 里查看详细编译日志的：&lt;a href=&quot;https://stackoverflow.com/questions/19014359/how-do-i-view-the-full-build-log-on-xcode5&quot;&gt;查看XCode编译日志&lt;/a&gt;。最后发现了它强制预先 include 的头文件是这个： &lt;code class=&quot;highlighter-rouge&quot;&gt;/System/Library/Frameworks/Cocoa.framework/Headers/Cocoa.h&lt;/code&gt;。 试了下所有类型报错都消失了，但又提示新的报错了：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:492:1: error: expected unqualified-id
@class NSString, Protocol;
^
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;看意思似乎是编译器不认识这个 &lt;code class=&quot;highlighter-rouge&quot;&gt;@&lt;/code&gt; 符号，这是 Objective-C++ 的语法，回头再去看看 XCode 的编译日志，找出了这句：&lt;code class=&quot;highlighter-rouge&quot;&gt;-x objective-c++&lt;/code&gt;, 查了下用于指定这些文件编译的时候被当做 Objective-C++ 文件用的。加上这句之后终于完美编译通过了。&lt;/p&gt;

&lt;p&gt;但是最终运行插件的时候 AE 提示无法加载插件，这就是第二个坑点了，我之前 &lt;code class=&quot;highlighter-rouge&quot;&gt;add_library()&lt;/code&gt; 里声明的类型是 SHARED，查看了一下 CMake 的编译日志（在它的缓存目录里有个 link.txt 文件），里面调用 clang 的时候用的是 &lt;code class=&quot;highlighter-rouge&quot;&gt;-dynamiclib&lt;/code&gt;, 而 XCode 这里调用参数是 &lt;code class=&quot;highlighter-rouge&quot;&gt;-boundle&lt;/code&gt;。查了下两者区别，前者是共享库，可以被编译器用来链接到别的可执行文件的，后者是专门用来被运行时加载的。其实 Window 平台并不区分这两者，都是 DLL，但是 Mac 平台上这两者是不一样的东西。AE 的插件需要的是后者。这两个参数在 CMake 里分别对应 SHARED 和 MODULE， 所以改成 MODULE 就好了。&lt;/p&gt;

&lt;p&gt;一顿折腾下来，最后的总结： 下次再遇到类似的问题，都直接看编译日志就行，不管什么编译系统，最终调用的语句都是类似的，比较最终的编译参数再回头去找各自编译系统里对应的设置方法即可，这是最直接有效的方式。&lt;/p&gt;

&lt;p&gt;当然，CMake 编译出来的只有一个模块文件，而 AE 插件在 Mac 上是需要一个 boundle 的壳的，就是一个 *.plugin 的文件夹，里面包含最终编译好的模块文件，plist描述文件，还有一些相关资源。这个直接用对应的 XCode 项目生成一个模板就行了，然后写个脚本每次编译结束自动替换模板里的模块文件。这些都做完了，就可以愉快地用 CLion 进行开发调试了。 ：）&lt;/p&gt;

&lt;p&gt;最后附上完整的工程：&lt;a href=&quot;../uploads/2017/aem-plugin.zip&quot;&gt;aem-plugin.zip&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 08 Jun 2017 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/851.html</link>
        <guid isPermaLink="true">https://idom.me/articles/851.html</guid>
        
        <category>C++</category>
        
        <category>工具软件</category>
        
        
      </item>
    
      <item>
        <title>调用Skia内置位图编解码器的坑点</title>
        <description>&lt;p&gt;Skia内置了对大部分图片格式的编解码支持，几乎所有格式都可以解码，编码主要支持：JPEG，PNG，WEBP等。然而想直接调用Skia去编解码图片，还有一些注意事项，这方面几乎没搜索到多少相关文档，一开始走了不少弯路，通过研究源码才一点一点走通了，这里总结一下方便遇到同样问题的人。&lt;/p&gt;

&lt;p&gt;先来看最常用的位图解码：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    bool decode(const void* bytes, size_t length, SkBitmap* image) {
        sk_sp&amp;lt;SkData&amp;gt; data = SkData::MakeWithoutCopy(bytes, length);
        SkAutoTDelete&amp;lt;SkCodec&amp;gt; codec(SkCodec::NewFromData(data.get()));
        if (!codec) {
            return false;
        }

        SkImageInfo info = codec-&amp;gt;getInfo();
        auto alphaType = imageInfo.isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;
        auto info = SkImageInfo::Make(imageInfo.width(), imageInfo.height(), kN32_SkColorType, alphaType);
        auto result = codec-&amp;gt;getPixels(info, image-&amp;gt;getPixels(), image-&amp;gt;rowBytes());
        return result == SkCodec::kSuccess;
    }

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面这个函数就能实现调用Skia对字节流数据进行位图解码。解释一下几个参数： bytes是位图文件的字节流首地址，这个很简单，通过各种标准文件系统接口就能读取。length就是这个字节流的长度，单位字节。image参数是一个SkBitmap对象，用于存储解码后的位图结果。在外部实例化一个空的SkBitmap，并把指针传进来就行。&lt;/p&gt;

&lt;p&gt;这里主要的坑点是对包含透明度的图片，Skia只支持预乘透明度格式（Premultiplied Alpha）的图片渲染。具体什么是 Premultiplied Alpha，大家可以自行谷歌一下，是一种常见的渲染上的性能优化方式。我们的位图文件通常都不是按 Premultiplied Alpha 方式存储的，所以这里 &lt;code class=&quot;highlighter-rouge&quot;&gt;codec&lt;/code&gt; 直接分析出来的 SkImageInfo 一般也是不能用的。所以要判断一下，图片是否含有透明通道，如果有的话，强制把 alphaType 改为 kPremul_SkAlphaType 的，也就是这句：&lt;code class=&quot;highlighter-rouge&quot;&gt;auto alphaType = imageInfo.isOpaque() ? kOpaque_SkAlphaType : kPremul_SkAlphaType;&lt;/code&gt; 这样解码出来的图片就能正确渲染了。&lt;/p&gt;

&lt;p&gt;再来看一下位图编码：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    void encode(const SkBitmap&amp;amp; image) {
        auto encodeType = SkImageEncoder::kJPEG_Type;
        auto compressionQuality = 92;
        SkDynamicMemoryWStream stream;
        auto result = SkImageEncoder::EncodeStream(&amp;amp;stream, image, encodeType, compressionQuality);
        if (result) {
            auto length = stream.bytesWritten();
            auto bytes = new uint8_t[length];
            stream.copyTo(bytes);
            // do something with the bytes ... 
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上面这个简单的示例展示了如何把一个 SkBitmap 对象编码为 JPEG 格式的图片文件字节流。当然你也可以把 &lt;code class=&quot;highlighter-rouge&quot;&gt;encodeType&lt;/code&gt; 改为其他的格式，比如：&lt;code class=&quot;highlighter-rouge&quot;&gt;SkImageEncoder::kPNG_Type&lt;/code&gt; 或 &lt;code class=&quot;highlighter-rouge&quot;&gt;SkImageEncoder::kWEBP_Type&lt;/code&gt;。&lt;code class=&quot;highlighter-rouge&quot;&gt;compressionQuality&lt;/code&gt; 表示图片的压缩质量，这个参数对PNG格式无效，因为PNG是无损的。最后我们会得到一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;bytes&lt;/code&gt; 字节流首地址，以及一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;length&lt;/code&gt; 字节流长度。需要存储文件就直接保存这个字节流即可。&lt;/p&gt;

&lt;p&gt;这里的坑点是你直接这么调用的话，结果会总是编码失败。断点进去看运行过程就会发现因为缺少对应类型的编码器，实际上一个编码器都不存在。这是因为 &lt;code class=&quot;highlighter-rouge&quot;&gt;SkImageEncoder&lt;/code&gt; 采用的是注册机制，这是被动调用的（你也可以注册自己的编码器进去）。虽然Skia默认提供了各种格式的编码器，也写好了注册代码，但是你自己的最终项目里因为没直接调用过那些编码器，只调用了 &lt;code class=&quot;highlighter-rouge&quot;&gt;SkImageEncoder&lt;/code&gt;, C++在链接你的程序的时候就会抛弃库里的实际编码器，因为你没调用过。所以解决方案是显式调用一下。好在Skia给我们写了一个专门用来欺骗编译器的宏。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;SkForceLinking.h&amp;gt;

__SK_FORCE_IMAGE_DECODER_LINKING;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;你只需要像上面那样，包含 &lt;code class=&quot;highlighter-rouge&quot;&gt;SkForceLinking.h&lt;/code&gt; 的头文件，然后在工程里任何一个地方加上这句宏的调用即可：&lt;code class=&quot;highlighter-rouge&quot;&gt;__SK_FORCE_IMAGE_DECODER_LINKING;&lt;/code&gt;。这样之前的代码就可以正常编码位图了。&lt;/p&gt;
</description>
        <pubDate>Wed, 02 Nov 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/850.html</link>
        <guid isPermaLink="true">https://idom.me/articles/850.html</guid>
        
        <category>C++</category>
        
        <category>图形渲染</category>
        
        
      </item>
    
      <item>
        <title>TypeScript编译器增强版</title>
        <description>&lt;p&gt;在 Egret 引擎的命令行工具里一直都有一个自动排序的功能，能够自动分析出ts文件的依赖关系并给出正确的加载顺序，不用开发者手动去排列加载顺序。但是跟引擎工具耦合在一起了，没法独立使用。并且因为不是基于语法树分析的，准确率虽然还不错，但是没有达到100%完全可靠。正好最近TypeScript 2.0也发布了，借着升级引擎内置编译器的机会，我把这个功能从Egret 命令行里抽离出来做成了独立版。这次是基于语法树分析的，实现了100%的可靠性。除了自动排序，还加上其他几项附加功能，相当于原版 TypeScript 编译器的增强版：&lt;a href=&quot;https://github.com/domchen/typescript-plus&quot;&gt;https://github.com/domchen/typescript-plus&lt;/a&gt;。 使用上完全兼容原版编译器，也是按照 tsc 项目的 API 风格去扩展的，额外增加的几个功能，参数开关默认都是关闭。&lt;/p&gt;

&lt;p&gt;主要提供了四个额外功能：getter/setter优化，类反射信息，自动排序，以及条件编译。其中自动排序和条件编译在TypeScript项目的官方issue一年前就有很多人提过，实际项目中确实也有强烈的需求，不过可能目前还不是TypeScript团队的核心目标，并且实现条件编译等功能会违反了他们现在的设计原则：不在输出JS过程中做类型检查操作。因此目前想等官方加上的可能性不太高。我会持续维护这个项目，尽量保持跟原版编译器每个版本的同步。&lt;/p&gt;

&lt;p&gt;下面详细说一下 typescript-plus 的使用：&lt;/p&gt;

&lt;h3 id=&quot;安装&quot;&gt;安装&lt;/h3&gt;

&lt;p&gt;首先确保你已经安装了最近版本的&lt;a href=&quot;http://nodejs.org/&quot;&gt;node.js&lt;/a&gt;。然后执行如下命令就可以安装了：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install -g typescript-plus
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果需要编程方式调用它，可以参考这个项目的代码：&lt;a href=&quot;https://github.com/domchen/tspack&quot;&gt;tspack&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;运行&quot;&gt;运行&lt;/h3&gt;

&lt;p&gt;直接在命令行调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;tsc-plus&lt;/code&gt; 即可；&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tsc-plus [input files] [options]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;命令行参数格式与原版tsc完全一致，关于原版编译器的使用方法，可以参考这个&lt;a href=&quot;https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Compiler%20Options.html&quot;&gt;教程&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;额外参数&quot;&gt;额外参数&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;参数&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;类型&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;默认值&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;accessorOptimization&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;boolean&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;false&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;如果get/set方法只含有一行对其他方法的调用，直接用那个目标方法作为get/set方法体&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;emitReflection&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;boolean&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;false&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;输出类的反射信息&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;reorderFiles&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;boolean&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;false&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;根据依赖关系自动排序源文件列表&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;defines&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Object&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;将代码中出现的全局变量替换为对应的常量&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这些参数除了 &lt;code class=&quot;highlighter-rouge&quot;&gt;defines&lt;/code&gt; 只能在tsconfig.json中使用外，其他参数还可以通过命令行传入，例如 &lt;code class=&quot;highlighter-rouge&quot;&gt;--reorderFiles&lt;/code&gt; 的形式。&lt;/p&gt;

&lt;p&gt;tsconfig.json 文件示例:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;compilerOptions&quot;: {
        &quot;module&quot;: &quot;commonjs&quot;,
        &quot;accessorOptimization&quot;: true,
        &quot;emitReflection&quot;: true,
        &quot;reorderFiles&quot;: true
        &quot;defines&quot;: {
            &quot;DEBUG&quot;: false,
            &quot;RELEASE&quot;: true
        }
    },  
    &quot;files&quot;: [
        &quot;core.ts&quot;,
        &quot;sys.ts&quot;
    ]  
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;gettersetter-优化&quot;&gt;Getter/Setter 优化&lt;/h3&gt;

&lt;p&gt;在TypeScript中，子类是没法覆盖父类的 get/set 方法的。为了解决这个问题，我们通常会转发一下 get/set 方法到本类的另一个方法，然后那个方法就可以被覆盖了，例如下面的例子：&lt;/p&gt;

&lt;p&gt;TypeScript:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Student {

    public _name:string;

    protected setName(value:string):void {
        this._name = value;
    }

    public get name():string {
        return this._name;
    }

    public set name(value:string) {
        this.setName(value);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这个方法确实能解决问题，但是也明显带来了性能问题，因为直接访问 get/set 属性时必须要额外转发了一次。现在我们只要开启 &lt;code class=&quot;highlighter-rouge&quot;&gt;accessorOptimization&lt;/code&gt; 选项，编译器就会优化掉只有一行代码调用的转发，直接用转发的目标方法作为 get/set  的方法体，上面的 TypeScript 文件开启这个参数后输出的 JavaScript 如下：&lt;/p&gt;

&lt;p&gt;Javascript:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var Student = (function () {
    function Student() {
    }
    Student.prototype.setName = function (value) {
        this._name = value;
    };
    Object.defineProperty(Student.prototype, &quot;name&quot;, {
        get: function () {
            return this._name;
        },
        set: Student.prototype.setName,
        enumerable: true,
        configurable: true
    });
    return Student;
}());
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;如果你把 &lt;code class=&quot;highlighter-rouge&quot;&gt;setName()&lt;/code&gt; 定义在了 &lt;code class=&quot;highlighter-rouge&quot;&gt;set name()&lt;/code&gt;之后，情况略复杂一些，但是一样可以正确输出，结果如下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var Student = (function () {
    function Student() {
    }
    Object.defineProperty(Student.prototype, &quot;name&quot;, {
        get: function () {
            return this._name;
        },
        set: setName,
        enumerable: true,
        configurable: true
    });
    Student.prototype.setName = setName;
    function setName(value) {
        this._name = value;
    };
    return Student;
}());
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;不管定义在什么位置，都可以正常运行。有了这个优化之后，就可以大胆地使用转发了，又解决了子类的覆盖问题，又完全不影响执行性能。&lt;/p&gt;

&lt;h3 id=&quot;类反射信息&quot;&gt;类反射信息&lt;/h3&gt;

&lt;p&gt;直接上例子：&lt;/p&gt;

&lt;p&gt;TypeScript:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;namespace ts {
    export interface IPerson {
        name:string;
    }
    
    export class Student implements IPerson {
        public name:string = &quot;&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;JavaScript:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var ts;
(function (ts) {
    var Student = (function () {
        function Student() {
            this.name = &quot;&quot;;
        }
        return Student;
    }());
    ts.Student = Student;
    __reflect(Student.prototype, &quot;ts.Student&quot;, [&quot;ts.IPerson&quot;]);
})(ts || (ts = {}));

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这个 &lt;code class=&quot;highlighter-rouge&quot;&gt;__reflect&lt;/code&gt; 函数跟 tsc 原版生成的 &lt;code class=&quot;highlighter-rouge&quot;&gt;__extends&lt;/code&gt; 辅助函数一样，一个文件都只会在文件顶部生成一次。它负责注册类的反射信息到类定义上。然后你就可以在运行时获取一个实例的完整类名或判断是否实现了某个接口。具体可以参考 Egret 引擎里的 &lt;code class=&quot;highlighter-rouge&quot;&gt;getQualifiedClassName()&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;is()&lt;/code&gt; 函数。&lt;/p&gt;

&lt;h2 id=&quot;自动排序&quot;&gt;自动排序&lt;/h2&gt;

&lt;p&gt;通常情况下，当我们传递了&lt;code class=&quot;highlighter-rouge&quot;&gt;--outFile&lt;/code&gt;参数后，编译器会把所有输入的ts文件列表编译到一个js文件中。然而这个输出的顺序是按照你输入文件列表的顺序来的，原版的ts编译器并不会去调整它，JS是一边加载一边执行的，如果有立即执行的代码，或者有类的继承，顺序不对加载过程就会报空引用错误。这导致我们需要手动去排列这个先后顺序。文件少的时候还无所谓，文件特别多的时候就很费劲了。&lt;/p&gt;

&lt;p&gt;现在只要开启 &lt;code class=&quot;highlighter-rouge&quot;&gt;reorderFiles&lt;/code&gt; 选项，编译器在输出JS文件前会自动分析ts代码的依赖关系，然后调整为一个正确的顺序输出结果。你不再需要手动调整输入文件的顺序，也不需要在ts文件里加 &lt;code class=&quot;highlighter-rouge&quot;&gt;///&amp;lt;reference/&amp;gt;&lt;/code&gt; 标签。我在常用的项目里都测试过了这个功能，目前还没发现任何问题。如果遇到非常特殊的写法是我没覆盖到的，可以到 &lt;a href=&quot;https://github.com/domchen/typescript-plus/issues&quot;&gt;https://github.com/domchen/typescript-plus/issues&lt;/a&gt; 去开一个issue，并贴一下能重现排序错误的源文件，我会尽快修复。&lt;/p&gt;

&lt;h3 id=&quot;条件编译&quot;&gt;条件编译&lt;/h3&gt;

&lt;p&gt;你可以使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;defines&lt;/code&gt; 参数来定义一系列的键值对。键表示代码中的&lt;strong&gt;全局变量&lt;/strong&gt;，值表示对应的常量（目前可以是布尔值，数字，或字符串）。在输出JS文件时，所有定义过的全局变量都会被替换为对应的常量。举个例子：&lt;/p&gt;

&lt;p&gt;tsconfig.json:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;compilerOptions&quot;: {
        &quot;defines&quot;: {
            &quot;DEBUG&quot;: false,
            &quot;LANGUAGE&quot;: &quot;en_US&quot;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;TypeScript:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;declare var DEBUG:boolean;
declare var LANGUAGE:string;

if (DEBUG) {
    console.log(&quot;DEBUG is true&quot;);
}

console.log(&quot;The language is : &quot; + LANGUAGE);

function someFunction():void {
    let DEBUG = true;
    if (DEBUG) {
        console.log(&quot;DEBUG is true&quot;);
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;JavaScript:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if (false) {
    console.log(&quot;DEBUG is true&quot;);
}

console.log(&quot;The language is : &quot; + &quot;en_US&quot;);

function someFunction() {
    var DEBUG = true;
    if (DEBUG) {
        console.log(&quot;DEBUG is true&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;大家应该会注意到，第二个在 &lt;code class=&quot;highlighter-rouge&quot;&gt;someFunction&lt;/code&gt; 函数里的 &lt;code class=&quot;highlighter-rouge&quot;&gt;if(DEBUG)&lt;/code&gt; 并没有替换，因为这个是局部变量，并不是全局变量。&lt;/p&gt;

&lt;p&gt;注意一下，编译器实际上只做了常量替换，并没有直接去掉 &lt;code class=&quot;highlighter-rouge&quot;&gt;if(false){....}&lt;/code&gt; 这些无法执行到的代码块。可能有人会有疑问，这哪能算条件编译啊？实际上因为编译后你还会用到其他的JS代码压缩工具，例如&lt;a href=&quot;http://lisperator.net/uglifyjs/&quot;&gt;UglifyJS&lt;/a&gt; 或 &lt;a href=&quot;https://developers.google.com/closure/compiler/&quot;&gt;Google Closure Compiler&lt;/a&gt;.
这些代码块用随便一个JS压缩工具都能很容易去掉，所以这里做到常量替换就已经足够了，不应该做编译器不需要做的额外工作。&lt;/p&gt;

&lt;h3 id=&quot;其他&quot;&gt;其他&lt;/h3&gt;

&lt;p&gt;实际上我还写了点代码压缩的优化，比如把 &lt;code class=&quot;highlighter-rouge&quot;&gt;Object.defineProperty(...)&lt;/code&gt; 这段固定的代码封装为短名函数，把&lt;code class=&quot;highlighter-rouge&quot;&gt;XXXX.prototype&lt;/code&gt; 缓存为短名局部变量。最终测试的效果减小的不是很明显，虽然压缩后的JS是小了一点，但是gzip之后是完全一样的。我们实际上网络传送的都是gzip之后的大小，所以意义并不大。最终还是去掉这个优化，保留原版TSC的输出格式。&lt;/p&gt;

&lt;p&gt;真正能显著压缩大小的一个思路其实是把private属性都重命名，这个也只有ts编译阶段可以做，因为private信息只有ts存在，到JS里都是public的。Uglifyjs这些工具遇到成员属性都当成public的，所以都跳过不压缩。还有一个思路就是基于tsc的语法树去实现混淆，把public，project以及类名，所有有效的字符串信息都重命名为随机段字符串。这样不存在可阅读的信息，项目规模足够大的情况，效果就跟编译成汇编一样了。以后有时间再研究吧。&lt;/p&gt;

&lt;p&gt;最后，你如果觉得这个项目还不错，给它加个star吧。 :D&lt;/p&gt;

</description>
        <pubDate>Fri, 14 Oct 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/849.html</link>
        <guid isPermaLink="true">https://idom.me/articles/849.html</guid>
        
        <category>JavaScript</category>
        
        <category>工具软件</category>
        
        
      </item>
    
      <item>
        <title>开启V8的JavaScript调试服务</title>
        <description>&lt;p&gt;V8的Javascript调试服务就是一个本地的Socket连接，嵌入V8的程序通过这个本地连接发送JSON字符串协议内容，与前端的调试工具进行交互。前端的调试工具通常是Chrome自带的开发者工具窗口，也有一些实现了V8调试协议的IDE，比如WebStorm，VSCode等。具体的调试协议内容可以参考这里：&lt;a href=&quot;https://github.com/v8/v8/wiki/Debugging-Protocol&quot;&gt;https://github.com/v8/v8/wiki/Debugging-Protocol&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然而V8默认并没有开启JavaScript调试服务，在嵌入V8到自己的应用程序后，不能断点调试JS会严重影响开发效率，因为你就只能用输出log的原始方式来调试。其实旧版本的V8是自带JS调试服务的，只是因为Chrome没有用到这个模块，在大约是 3.26 版本之后就移除了。大家可以检出旧版本的v8代码库找到debug-agent.*文件就是了。但是V8里的DebugAgent引用了大量内部其他模块，想直接用还是很费劲的，我花了点时间把这块的代码已经精简抽离出来了。放在了：&lt;a href=&quot;https://github.com/domchen/V8Performace/tree/master/src/debug&quot;&gt;https://github.com/domchen/V8Performace/tree/master/src/debug&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;其中主要的是 DebugAgent 这个类。另外两个Socket和Log是看名字就知道是干嘛用的，可以替换自己项目里的对应类。DebugAgent 的用法很简单，在初始化V8环境后，执行如下这一句就可以了：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DebugAgent::Enable(&quot;MyApp&quot;, 5959, true);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DebugAgent::Enable&lt;/code&gt; 方法的三个参数含义分别是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;hostName: 应用程序的名称，最终会显示在调试协议的头里。&lt;/li&gt;
  &lt;li&gt;port: 调试协议要监听的本地端口。&lt;/li&gt;
  &lt;li&gt;waitForConnection： 是否阻塞程序等待调试工具连接，设置为true可以让要执行的JS代码在第一行断点。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;具体的使用示例可以参考前面发的github项目里的Main.cpp文件。&lt;/p&gt;

&lt;p&gt;到这里程序里的V8调试服务就已经开启了。现在还需要一个前端的可视化调试工具。最好用的调试工具当然是Chrome自带的开发者工具啦，可是Chrome的远程调试只支持Android，并不支持这种自己嵌入的V8调试。好在我们可以复用NodeJS的调试利器Node-Inspector。Node-Inspector把Chrome的开发者工具做成了独立的浏览器应用，对它进行一下改造，去掉专门为NodeJS设计的接口就可以做为通用的V8前端调试工具。这部分脏活累活显然我也干完了。大家只要用npm在线安装一下我封装好的命令行工具v8-inspector即可。（NPM是NodeJS的包管理器，如果不清楚使用方法，请自行谷歌。）安装命令：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install -g v8-inspector
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;然后只要简单的在命令行执行：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;v8-inspector
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;命令，然后它就会自动打开一个Chrome浏览器窗口。默认URL是: http://127.0.0.1:8888/?port=5959。如果之前嵌入V8的程序已经启动并在阻塞等待中，这时候你应该就能直接看到那个程序里的JS文件列表，并断点在第一行了。如果程序是后启动的，没关系，F5刷新一下浏览器标签即可。URL里的 port=5959 就对应你之前给 &lt;code class=&quot;highlighter-rouge&quot;&gt;DebugAgent::Enable&lt;/code&gt; 传入的 &lt;code class=&quot;highlighter-rouge&quot;&gt;port&lt;/code&gt; 参数值。要修改这个端口，例如改为7878，执行下面的命令即可：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;v8-inspector --debug-port=7878
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;你同时也可以改为监听远程的ip地址，执行下面的命令：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;v8-inspector --debug-port=7878 --debug-host=192.168.1.29
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Fri, 29 Jul 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/848.html</link>
        <guid isPermaLink="true">https://idom.me/articles/848.html</guid>
        
        <category>C++</category>
        
        <category>V8</category>
        
        
      </item>
    
      <item>
        <title>V8里又有一大波即将废弃的API</title>
        <description>&lt;p&gt;相关链接：&lt;a href=&quot;https://groups.google.com/forum/#!topic/v8-users/gQVpp1HmbqM&quot;&gt;https://groups.google.com/forum/#!topic/v8-users/gQVpp1HmbqM&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这次API变动涉及的规模非常之庞大，绝大部分我们会使用到的接口都要更新一遍。这里简单解读一下变动的原因以及新接口的使用方法。首先来看下面这个示例：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Local&amp;lt;Value&amp;gt; x = some_value;
Local&amp;lt;String&amp;gt; s = x.ToString(); // 方法内部如果发生异常会返回空Handle
s-&amp;gt;Anything(); // 访问空Handle导致崩溃!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这是一段非常常见的代码，我们有个类型是 &lt;code class=&quot;highlighter-rouge&quot;&gt;Local&amp;lt;Value&amp;gt;&lt;/code&gt; 的变量 &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt;,现在需要对它调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;toString()&lt;/code&gt; 方法来获取字符串。如果 &lt;code class=&quot;highlighter-rouge&quot;&gt;toString()&lt;/code&gt; 方法因为栈内存不足或者其他任何原因发生异常，这时候返回的 &lt;code class=&quot;highlighter-rouge&quot;&gt;Local&amp;lt;String&amp;gt;&lt;/code&gt; 就会是一个空Handle，而接着访问空Handle就会导致程序崩溃。正确的写法应该是这样：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if(!s.IsEmpty()）{
    s-&amp;gt;Anything(); // 判断不是空Handle才执行后续代码
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;使用Local Handle前我们需要加一个&lt;code class=&quot;highlighter-rouge&quot;&gt;IsEmpty()&lt;/code&gt;的判断，阻止对空Handle的访问才可以避免程序崩溃。问题就出在这个API Style 上，V8现有的API没能有效的提醒开发者哪些返回值是需要检查 &lt;code class=&quot;highlighter-rouge&quot;&gt;IsEmpty()&lt;/code&gt; 的。由于返回都是Local Handle，再加上开发者自己的函数也传递的各种 Local Handle，如果所有 Local Handle 使用前都检查一次太不现实了。所以实际情况是大家有的检查有的不检查，大大增加了整个系统崩溃的概率。&lt;/p&gt;

&lt;p&gt;为了解决这个问题，V8引入了一组新的接口：&lt;code class=&quot;highlighter-rouge&quot;&gt;Maybe&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;MaybeLocal&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;旧API里类似这样的函数：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;double NumberValue();
Local&amp;lt;String&amp;gt; ToString();

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;都会变成这样:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Maybe&amp;lt;double&amp;gt; NumberValue(Local&amp;lt;Context&amp;gt;);
MaybeLocal&amp;lt;String&amp;gt; ToString(Local&amp;lt;Context&amp;gt;);

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;总结就是：凡是可能返回空Handle的API，都改成了返回 Maybe Handle，而不是 Local Handle。MaybeLocal的意思就是可能有值，也可能是空的。将 Maybe Handle 转换为 Local Handle 使用如下方法：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MaybeLocal&amp;lt;Value&amp;gt; x = some_value;
if(x.IsEmpty()){
    Local&amp;lt;Value&amp;gt; s = x.ToLocalChecked();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;注意这里如果不判断 &lt;code class=&quot;highlighter-rouge&quot;&gt;IsEmpty()&lt;/code&gt;，如果 x 确实是空的，&lt;code class=&quot;highlighter-rouge&quot;&gt;ToLocalChecked()&lt;/code&gt; 方法就会强制抛出一个异常。只有你非常肯定 x 确定不是空的，才可以省略 &lt;code class=&quot;highlighter-rouge&quot;&gt;IsEmpty()&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;可以看出这套新API可以有效提醒开发者对空的 Handle 进行检查，因为你必须把 Maybe Handle 转换为 Local Handle 才可以使用，转换的过程再加上 &lt;code class=&quot;highlighter-rouge&quot;&gt;IsEmpty()&lt;/code&gt; 的判断，可以从机制上避免写出不稳定的系统。到目前为止，V8里绝大部分API已经都改成了返回 Maybe Handle 的形式。根据原文的描述，等全部改完，会有六周的共存时间，然后直接返回 Local Handle 的旧API会全部移除。所以大家从现在开始全都使用返回 Maybe Handle 的版本吧。&lt;/p&gt;

&lt;p&gt;这里最后提一下为何返回 Maybe Handle 的版本都多了一个 Context 参数？这里主要是因为要重载旧API，只有返回值不同在C++里不算合法重载，所以加了个Context参数，哭。不过在全局声明一个 Context 用就行，也不是什么难事。&lt;/p&gt;

</description>
        <pubDate>Fri, 22 Jul 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/847.html</link>
        <guid isPermaLink="true">https://idom.me/articles/847.html</guid>
        
        <category>C++</category>
        
        <category>V8</category>
        
        
      </item>
    
      <item>
        <title>V8中JS与C++性能测试对比</title>
        <description>&lt;h2 id=&quot;测试方法&quot;&gt;测试方法&lt;/h2&gt;

&lt;p&gt;预先在JS端构造1000w组随机数据，每组数据四个数值，代表：x,y,width,height。然后将所有测试数据传入Native，确保JS和Native测试的数据是完全相同的。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var data = [];
var i, t, x, y, width, height, index, start;
for (i = 0; i &amp;lt; SIZE; i++) {
    x = parseInt(Math.random() * 100);
    y = parseInt(Math.random() * 100);
    width = parseInt(Math.random() * 100);
    height = parseInt(Math.random() * 100);
    data.push(x, y, width, height);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;分别在JS端和Native遍历这1000w组数据，执行Matrix.transformBounds(rect)方法以及transformBoundsNoField(x,y,width,height)函数。前者是一个类方法，执行过程会读写类实例上的属性。后者是纯数学函数，不带任何属性读写。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var index = 0;
for (var i = 0; i &amp;lt; SIZE; i++) {
    var x = data[index++];
    var y = data[index++];
    var width = data[index++];
    var height = data[index++];
    rectJS.setTo(x, y, width, height);
    matrixJS.transformBounds(rectJS);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;var index = 0;
for (var i = 0; i &amp;lt; SIZE; i++) {
    var x = data[index++];
    var y = data[index++];
    var width = data[index++];
    var height = data[index++];
    transformBoundsNoField(x, y, width, height);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;测试结果&quot;&gt;测试结果&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;JS耗时:509ms（纯JS测试耗时，执行JS版的Matrix.transformBounds类方法）&lt;/li&gt;
  &lt;li&gt;JS-NoField耗时:441ms（不含属性读写的纯JS测试耗时,执行JS版的transformBoundsNoField函数）&lt;/li&gt;
  &lt;li&gt;JSBinding耗时:700ms（绑定方式测试耗时，将原生端的Matrix类绑定为JS对象执行Matrix.transformBounds方法）&lt;/li&gt;
  &lt;li&gt;Native耗时:188ms（纯Native测试耗时，执行Native版的Matrix.transformBounds类方法）&lt;/li&gt;
  &lt;li&gt;Native-NoField耗时:188ms（不含属性读写的纯Native测试耗时，执行Native版本的transformBoundsNoField函数）&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;测试结论&quot;&gt;测试结论&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;从测试结果（1）与（2）对比可以看出JS端不读写属性会有约15%性能提升。&lt;/li&gt;
  &lt;li&gt;从测试结果（4）与（5）对比可以看出Native端是否读写属性没有任何区别，属性读写在Native端的开销基本可以忽略。&lt;/li&gt;
  &lt;li&gt;从测试结果（3）跟其他结果对比可以看出无论哪种情况，JS绑定Native对象的方式都是最慢的。&lt;/li&gt;
  &lt;li&gt;Native端运行性能大约是JS端的 2.3~2.7 倍。其中测试（1）含有属性读写的情况更加符合实际使用场景。2.7倍左右更有参考价值。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;测试demo&quot;&gt;测试Demo&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/domchen/V8Performace&quot;&gt;https://github.com/domchen/V8Performace&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 20 Jun 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/845.html</link>
        <guid isPermaLink="true">https://idom.me/articles/845.html</guid>
        
        <category>C++</category>
        
        <category>V8</category>
        
        <category>JavaScript</category>
        
        
      </item>
    
      <item>
        <title>在Mac下使用libc++编译v8</title>
        <description>&lt;p&gt;Mac从10.7开始，系统默认的标准库改成了&lt;strong&gt;libc++&lt;/strong&gt;，支持c++11标准，系统里也还有之前的libstdc++，但它是一个固定的旧版本，不支持c++11标准。虽然大的趋势是都使用&lt;strong&gt;libc++&lt;/strong&gt;，但是还有不少老项目在使用libstdc++编译，导致跟其它库混合使用的时候可能会出现链接错误。chrome v8 就是这样，默认的编译配置是依赖libstdc++的而不是&lt;strong&gt;libc++&lt;/strong&gt;，导致编译出来的静态库放到Skia项目里无法直接使用（Skia默认编译配置是依赖&lt;strong&gt;libc++&lt;/strong&gt;）。要解决这个问题，我们编译一个&lt;strong&gt;libc++&lt;/strong&gt;版的v8静态库即可。&lt;/p&gt;

&lt;p&gt;在终端进入v8目录后，分别执行以下命令就可以了：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export CXX=&quot;/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -std=c++11 -stdlib=libc++&quot;

export LINK=&quot;/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -std=c++11 -stdlib=libc++&quot;

export GYP_DEFINES=&quot;clang=1 mac_deployment_target=10.7&quot;

make native
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;目前只找到使用 make 命令的这种方式可以生成&lt;strong&gt;libc++&lt;/strong&gt;版的v8静态库，使用 gyp/ninja 的还不行。所以编译过程会比较慢，耐心等一会，然后在 &lt;code class=&quot;highlighter-rouge&quot;&gt;out/native/&lt;/code&gt; 下就可以找到生成的静态库。&lt;/p&gt;

&lt;p&gt;很奇怪，后来看了下chromium项目里，在Mac上也是直接用&lt;strong&gt;libc++&lt;/strong&gt;编译v8的，但是v8项目的默认编译配置却是依赖libstdc++的，不知道是不是来不及更新还是有历史原因。理论上，如果用libc++编译的话，Mac最低就只能支持到10.7版本以上。又看了一下chrome的下载页面，发现它在Mac上最低支持版本居然是10.9。所以应该完全不用考虑10.6了，全都使用&lt;strong&gt;libc++&lt;/strong&gt;编译合情合理。&lt;/p&gt;

</description>
        <pubDate>Sun, 12 Jun 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/844.html</link>
        <guid isPermaLink="true">https://idom.me/articles/844.html</guid>
        
        <category>C++</category>
        
        <category>V8</category>
        
        
      </item>
    
      <item>
        <title>使用代理同步谷歌项目时出现文件下载失败</title>
        <description>&lt;p&gt;在按官方文档使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;gclient sync&lt;/code&gt; 命令同步谷歌v8或Chromium项目时，最后总是会出现文件下载失败的报错：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Failed to fetch file gs://chromium-gn/a68b194afb05d6a6357cf2e2464136ed7723c305 for src/buildtools/mac/gn.
...
Error: Command 'download_from_google_storage --no_resume --platform=darwin --no_auth --bucket chromium-gn -s src/buildtools/mac/gn.sha1' returned non-zero exit status ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;搜索了一下午才找到解决方案，是翻墙代理的问题， &lt;code class=&quot;highlighter-rouge&quot;&gt;gclient sync&lt;/code&gt; 里的某些子命令似乎在代理环境下无法正确连接谷歌服务器，准确说是连接上了，但是验证会卡非常长时间然后验证失败无法下载。一开始以为是代理没配置好，专门给命令行也配置了代理依然不行。这个问题如果你上谷歌官方issue和论坛里去找几乎找不到正确解决方案，因为他们大部分人不需要翻墙，在天朝写个代码真不容易。&lt;/p&gt;

&lt;p&gt;这个问题不知道谷歌会不会修好，如果一直没修自己手动也可以解决。本质上只是谷歌自己的命令行工具无法下载，但是你通过浏览器或者其他命令行工具是能下载文件的。以上面的报错为例，命令行需要把这个文件：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gs://chromium-gn/a68b194afb05d6a6357cf2e2464136ed7723c305
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;下载到这里并命名为 &lt;code class=&quot;highlighter-rouge&quot;&gt;gn&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;src/buildtools/mac/gn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;我们只需要 &lt;code class=&quot;highlighter-rouge&quot;&gt;gs://&lt;/code&gt; 替换为 &lt;code class=&quot;highlighter-rouge&quot;&gt;https://storage.googleapis.com/&lt;/code&gt; 就可以直接在浏览器下载了。前提当然是你还开着翻墙代理。所以任务就变成了下载下面这个地址的文件到刚刚那个目录。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://storage.googleapis.com/chromium-gn/a68b194afb05d6a6357cf2e2464136ed7723c305
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;你可以一个个用浏览器下载，也可以用命令行工具如 &lt;code class=&quot;highlighter-rouge&quot;&gt;wget&lt;/code&gt; 去下载：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget --no-check-certificate https://storage.googleapis.com/chromium-gn/a68b194afb05d6a6357cf2e2464136ed7723c305 -O src/buildtools/mac/gn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;可以写个工具脚本，一旦遇到 &lt;code class=&quot;highlighter-rouge&quot;&gt;download_from_google_storage&lt;/code&gt; 错误的时候就执行一次手动下载，直到再次执行 &lt;code class=&quot;highlighter-rouge&quot;&gt;gclient sync&lt;/code&gt; 不再报错为止，问题就解决了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;「更新」&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我花了点时间用 NodeJS 写了个全自动下载的命令行工具。点击此处下载：&lt;a href=&quot;../uploads/2016/gs.zip&quot;&gt;gs.zip&lt;/a&gt;。下载后要在系统安装一下 NodeJS 就可以用了。使用方式很简单，如果在mac下，下载这个文件解压后,放到你上次执行 &lt;code class=&quot;highlighter-rouge&quot;&gt;gclient sync&lt;/code&gt; 的那个目录下，也就是v8或src(chromium)文件夹的上一级。先执行一下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;chmod +x ./gs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;让它有可执行权限。然后直接执行：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./gs src/DEPS
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;只有一个参数，输入项目中DEPS这个文件的路径给它。不管相对路径还是绝对路径都可以。然后你就应该会看到一系列如下的输出：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;downloading... https://xxx  to  xxx

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;等它全部下载完就好了。然后你再继续执行 &lt;code class=&quot;highlighter-rouge&quot;&gt;gclient sync&lt;/code&gt; 应该就没问题了。
如果在windows下，把那个gs加个 &lt;code class=&quot;highlighter-rouge&quot;&gt;.js&lt;/code&gt; 的扩展名，然后用 &lt;code class=&quot;highlighter-rouge&quot;&gt;node&lt;/code&gt; 命令执行它即可,类似这样：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;node gs.js src/DEPS
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;其他步骤同上。&lt;/p&gt;
</description>
        <pubDate>Tue, 07 Jun 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/843.html</link>
        <guid isPermaLink="true">https://idom.me/articles/843.html</guid>
        
        <category>C++</category>
        
        
      </item>
    
      <item>
        <title>Mac上直接解压C++静态库的问题</title>
        <description>&lt;p&gt;这段时间在研究C++的各种编译构建过程，学习了一下cmake，gyp/ninja这些自动化构建工具后，想着自己试下用纯命令行跑一遍编译流程。在试图把C++静态库编译为动态库的过程中遇到了棘手的问题，找了好久，最后发现是跟Mac平台相关的，这里记录一下，希望对遇到类似问题的童鞋有帮助。&lt;/p&gt;

&lt;p&gt;C++的静态库(*.a文件)就是一个压缩包，把所有 *.o 文件打包在里面。所以我想尝试做的事很简单：就是把静态库里的 *.o 文件都解压出来，然后在用这些 *.o 文件链接合并为一个动态库。我直接双击解压的，这样就得到了一堆的 *.o 文件。然后我执行了生成动态库的命令，类似如下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;c++ -g -dynamiclib -Wl,-headerpad_max_install_names  -o libtest.dylib /usr/lib/libexpat.dylib /usr/lib/libz.dylib -framework ApplicationServices -framework OpenGL *.o  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;结果一直报错：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ld: file not found: raw_codec.SkRawAdapterCodec.o
clang: error: linker command failed with exit code 1 (use -v to see invocation)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;报错说找不到这个&lt;code class=&quot;highlighter-rouge&quot;&gt;raw_codec.SkRawAdapterCodec.o&lt;/code&gt;文件，但是我确定文件是存在的。根据提示加了个&lt;code class=&quot;highlighter-rouge&quot;&gt;-v&lt;/code&gt;参数，打印了详细的列表，发现这个&lt;code class=&quot;highlighter-rouge&quot;&gt;raw_codec.SkRawAdapterCodec.o&lt;/code&gt;是第一个要加载的文件，说明可能所有文件都没被命令行识别。&lt;/p&gt;

&lt;p&gt;我接着测试了其他的命令，单独对这一个&lt;code class=&quot;highlighter-rouge&quot;&gt;raw_codec.SkRawAdapterCodec.o&lt;/code&gt;进行链接，不管什么参数都提示 &lt;code class=&quot;highlighter-rouge&quot;&gt;ld: file not found&lt;/code&gt; 的错误。看来就是文件无法被加载。然后想着去项目原始目录里找被打包为静态库前的这个 *.o 文件，一测试居然成功了没报错！说明是从静态库里解压出来的 *.o 文件有问题。于是二进制对比两个文件，发现MD5是完全一致的，也就是说文件内容是没问题的。那么就是权限问题咯？把两个文件放到同一个目录下，用&lt;code class=&quot;highlighter-rouge&quot;&gt;ls -l&lt;/code&gt;命令查看了一下，输出如下信息：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-rw-r--r--  1 dom  staff   734032  5 25 11:35 raw_codec.SkRawAdapterCodec2.o
-rw-r--r--@  1 dom  staff  734032  5 25 10:25 raw_codec.SkRawAdapterCodec.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;下面那个文件是出问题的文件，权限里居然出现了一个@，谷歌了一下，说这个是mac平台上的扩展属性标识，说明除了标准权限外还有其他的。可以用&lt;code class=&quot;highlighter-rouge&quot;&gt;ls -@l&lt;/code&gt;命令查看具体是什么扩展属性，输出如下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-rw-r--r--  1 dom  staff   734032  5 25 11:35 raw_codec.SkRawAdapterCodec2.o
-rw-r--r--@  1 dom  staff  734032  5 25 10:25 raw_codec.SkRawAdapterCodec.o
	com.apple.quarantine	   29 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这个&lt;code class=&quot;highlighter-rouge&quot;&gt;com.apple.quarantine&lt;/code&gt;是什么鬼呢？继续搜索，原来是我们经常看到的那个提示：「”xxx”是从互联网下载的应用程序。您确定要打开它吗？」。算是一种安全限制，在Mac OSX 10.5开始引入了这个属性，如果从浏览器下载，或使用系统的解压命令比如tar，zip等，都会自动给文件加上这个属性，导致第一打开需要弹窗允许。所以我们一直无法加载到这个&lt;code class=&quot;highlighter-rouge&quot;&gt;raw_codec.SkRawAdapterCodec.o&lt;/code&gt;是因为它含有&lt;code class=&quot;highlighter-rouge&quot;&gt;com.apple.quarantine&lt;/code&gt;扩展属性。要删除这个属性可以使用命令：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xattr -d com.apple.quarantine 文件名 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;或者直接删除整个文件夹里所有文件的这个属性：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xattr -dr com.apple.quarantine 文件夹名  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;测试了一下，删除&lt;code class=&quot;highlighter-rouge&quot;&gt;com.apple.quarantine&lt;/code&gt;属性后果然好了。其实更规范的解压静态库的方式是使用&lt;code class=&quot;highlighter-rouge&quot;&gt;ar -x&lt;/code&gt;命令,使用ar命令就不会自动添加&lt;code class=&quot;highlighter-rouge&quot;&gt;com.apple.quarantine&lt;/code&gt;属性了。可以批量解压一个文件夹下的所有 *.a 文件，在指定目录下执行这条命令即可：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ls *.a | xargs -n1 ar -x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;最后测试了一下之前的命令，成功生成了动态库，大功告成~&lt;/p&gt;
</description>
        <pubDate>Wed, 25 May 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/842.html</link>
        <guid isPermaLink="true">https://idom.me/articles/842.html</guid>
        
        <category>C++</category>
        
        
      </item>
    
      <item>
        <title>显示列表与脏矩形渲染</title>
        <description>&lt;p&gt;这篇文章主要讲解显示列表与脏矩形渲染的基础概念，希望这部分内容对大家理解2D屏幕渲染过程会有帮助。:D&lt;/p&gt;

&lt;h2 id=&quot;什么是显示列表&quot;&gt;什么是显示列表？&lt;/h2&gt;

&lt;p&gt;在2D图形渲染中，显示列表是非常成熟的一种渲染组织方式，它封装了晦涩难以操作的底层绘图接口，提供给开发者一套非常简易且高效的API，来组织显示元素最终呈现到屏幕上。如下图，是一个简单的显示列表示例。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/841/1.png&quot; alt=&quot;显示列表结构示例&quot; /&gt;&lt;/p&gt;

&lt;p&gt;显示列表是一个树状的结构，列表中的每个节点叫做显示对象。显示对象分为两类，一类是普通显示对象，另一类是容器显示对象。容器显示对象可以在内部再包含其他的显示对象（普通或容器）。可以将容器显示对象类比为一个盒子，里面放上其他的显示对象。当你移动容器时，它内部的子项也会整体跟着移动，当你缩放容器时，它内部的子项也会整体跟着缩放。显示列表就是这样一层一层嵌套而成的数据结构。容器通常都只起到整体定位作用，并不呈现图像。在末端叶子节点的普通显示对象通常是图片，可以显示到屏幕上。当然可以显示的节点也有可能是文本或矢量图，为了简单起见，这里都以图片为例。一个图片节点的所有父级容器上的定位或缩放信息，最终都会应用到这个图片上，用来确定图片在屏幕上最终的显示位置和区域大小。&lt;/p&gt;

&lt;h2 id=&quot;全屏刷新渲染&quot;&gt;全屏刷新渲染&lt;/h2&gt;

&lt;p&gt;基于显示列表结构的渲染方式，最常的就是全屏刷新模式。这也是目前大多数游戏引擎普遍采用的方案，因为不管是原理还是实现都比较简单：首先设定一个时钟频率，例如通常是每秒执行60次。每次都单独刷新一次屏幕。刷新过程就是直接清空整个屏幕，然后从显示列表的根节点开始遍历，按顺序找到每个可呈现的显示对象节点，按照它的坐标和大小绘制到屏幕上。这样开发者只需要改变显示对象的位置属性，等待下一次时钟周期到来，改变就会自动刷新到屏幕上。由于通常情况下，并不是每秒60次显示列表每次都会发生改变，或者发生改变时仅有一小部分改变。因此清空整个屏幕重绘的方式虽然实现简单，但是不必要的开销比较大。&lt;/p&gt;

&lt;h2 id=&quot;脏矩形渲染&quot;&gt;脏矩形渲染&lt;/h2&gt;

&lt;p&gt;脏矩形渲染是一种基于显示列表的局部刷新方法。依然是要有一个时钟频率，定时每秒执行60次。但区别是每次我们并不直接清空整个屏幕，而是首先计算屏幕上发生改变的区域，这里我们叫做重绘区，然后只清空指定的重绘区，并找出跟这个区域相交的所有显示对象重绘一遍。如果显示列表本次美并没有发生改变，那么将直接跳过本次绘制，什么也不做。&lt;/p&gt;

&lt;p&gt;显而易见的是它能大幅提高屏幕整体渲染性能，特别是对于复杂UI界面的情况，全屏刷新算法会每秒60次不停地刷新所有UI对象，在有脏矩形渲染的情况下，哪改变绘制哪，极端情况直接跳过绘制，在复杂UI界面的情况非常容易达到满帧。另外脏矩形渲染能够节省设备电量以及降低发热量。我们曾经测试过同一个线上游戏，在更新到脏矩形渲染后，整体耗电量降到了原先的30%，发热量也从45度降低到了35度，结果还是非常显著的。&lt;/p&gt;

&lt;h2 id=&quot;如何局部刷新&quot;&gt;如何局部刷新&lt;/h2&gt;

&lt;p&gt;这里简单说一下实现局部刷新的底层接口。如果使用的是Canvas2DRenderingContex，比较简单：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;context.save();
context.clearRect(region.minX, region.minY, region.width, region.height);
context.rect(region.minX, region.minY, region.width, region.height); context.clip();
//在此绘制相关的显示对象...
context.restore();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;开始前先save()保存上下文环境，然后用clearRect()清空目标区域像素，然后用rect绘制一个相同的矩形路径（可以自行多次绘制，区域会叠加），接着clip()一下，之后所有的绘制操作都会自动被限制在rect的区域内。只要按正常方式去把跟重绘区相交的对象都绘制一遍就好了。最后是调用restore()取消clip()的限制。&lt;/p&gt;

&lt;p&gt;如果使用的WebGL或OpenGL，步骤略复杂一些，原理是开启Stencil模板缓冲，模拟clip()方法即可，mask遮罩功能也是用相同的方式实现的。这里不再赘述。&lt;/p&gt;

&lt;h2 id=&quot;获取重绘区&quot;&gt;获取重绘区&lt;/h2&gt;

&lt;p&gt;接下来，局部刷新算法的重点就集中在了如何获取重绘区上。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/841/2.png&quot; alt=&quot;显示对象在屏幕上的矩形区域&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图，每个显示对象在屏幕上都对应一个矩形区域。通常它自身都包含两个属性：自身矩阵和自身矩形。自身矩阵代表这个显示对象在父级容器中的位置，缩放等变换信息，也就是开发者最通常操作的部分。自身矩形是固定测量出来的一个四边形，如果是一个图片，自身矩形等于这个图片未缩放时的原始大小区域。自身矩形和自身矩阵叠加，会得到这个显示对象在它父级容器里变换后（例如位移或缩放）的矩形区域。但是为了能统一比较，我们需要获取每个显示对象在屏幕上的矩形。那么首先是要获得这个显示对象相对于屏幕的矩阵信息，屏幕矩阵等于自身矩阵和所有父级容器的矩阵相乘结果。最后用屏幕矩阵跟自身矩形叠加就能得到屏幕矩形。&lt;/p&gt;

&lt;p&gt;获取到每个显示对象在屏幕上的矩形区域后，我们下一步是需要一个通知机制，每当这个显示的对象的屏幕矩阵和自身矩形发生改变的时候，就重新计算一次屏幕矩形。这时候就能得到两个矩形：这个显示对象改变前在屏幕上的矩形，和改变后在屏幕上的矩形。这两个矩形就是要获取的重绘区。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/841/3.png&quot; alt=&quot;获取重绘区域&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图，一张图片从屏幕的左边移动到了右边，那么这个操作会产生左右两个需要重绘的区域。将两个区域都清空，并根据图片新的位置绘制到新区域上，这样图片就从旧位置移动到了新位置。&lt;/p&gt;

&lt;h2 id=&quot;合并重绘区&quot;&gt;合并重绘区&lt;/h2&gt;

&lt;p&gt;上图是单个显示对象移动的情况，缩放的情况也可以此类推。但实际情况中，每次刷新时并不只有一个显示对象改变，可能会产生无数个可能相交的重绘区域。最简单的范式是直接把这大量的矩形依次拿去重绘，分别执行清空矩形，然后查找相交的对象重绘。显然重绘区数量过大的时候，会造成巨大的开销。因此我们还需要对得到的重绘区列表进行合并。这里就进入到了脏矩形算法的核心了，合并的算法规则是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;如果两个矩形合并后总面积小于两个矩形各自面积之和，则允许合并，并且优先合并相交面积最大的两个矩形。&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;合并到最后，若规则1已经不满足，但是剩余矩形总数量大于3，则强制继续合并。并优先合并面积增加量最小的两个矩形。&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;剩余矩形总数为什么控制为3以内？这其实是一个测试结论，把合并到最后重绘区数量控制为三个以内的时候是最佳的平衡点。既不会对接下来的重绘操作带来压力，也足够覆盖大部分的局部刷新情况。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../img/841/4.png&quot; alt=&quot;合并重绘区域&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图，屏幕上有大量的重绘区域，我们根据刚刚的合并原则， 能够得出最终的三个重绘区。注意一下左下角的重绘区，实际上内部两个矩形并没有相交。也就是说他们合并后的矩形肯定大于各自的面积之和。这里正是使用了规则2将其合并的。再极端一些，整个屏幕布满了零碎的矩形都不相交，那么这个算法结果会得到唯一的一个全屏矩形。这样正好符合全屏刷新的需求，能让这个合并算法同时适应局部刷新和全屏刷新的情况。&lt;/p&gt;

&lt;p&gt;到这里的话，脏矩形渲染的基本原理就算讲解完了，其实cacheAsbitmap位图缓存的设计远比这块复杂，但限于篇幅在此就不赘述了。&lt;/p&gt;

&lt;h2 id=&quot;脏矩形渲染faq&quot;&gt;脏矩形渲染FAQ&lt;/h2&gt;

&lt;p&gt;这里有几个问题是经常被问到的，统一整理一下回答。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.都说脏矩形渲染性能高，但我写了个全屏动画的测试对比发现脏矩形渲染并没有全屏刷新性能高，这是怎么回事？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;答：脏矩形渲染之所以性能高，是要放在实际项目中才有意义的。它的原理是消耗1%以下的计算性能，换取绝大多数情况下性能成倍的提升。因此在少数极端情况确实会落后一点点，但是绝大部分情况下性能都好的多，我们认为这个兑换是完全值得的。你如果写个全屏无数动画同时播放的性能对比测试，实际上就是在拿少数的极端情况做测试，因为全屏都在改变，无论什么算法都不可能比「没有算法」来得快。这种情况下，脏矩形渲染是会比全屏刷新模式有个一两帧的差距，但差距也不是特别明显。而且我们还在做一些进一步的优化：在接近全屏渲染情况时自动放弃后续的计算而采用全屏刷新方式。总之还是应该看实际项目中的整体帧率。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.浏览器本身就有脏矩形渲染，自己再实现一遍有什么意义吗？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;答：浏览器里大体可以分为两大类接口集合，第一类是偏向上层的以HTML/CSS为主的页面排版接口，第二类是偏向底层的以Canvas/WebGL为主的绘图接口。第一类是具有原生的脏矩形渲染，但是由于功能体系过于庞大，加上本身也是设计给偏向静态的页面排版使用的，性能上第一类远远不及第二类。因此偏向高动态交互的游戏引擎，全都选择了基于第二类接口，从最底层开始构建高性能的渲染，其实这种方案跟原生开发的界限已经很模糊了，也更容易实现跨平台移植。早期没有脏矩形渲染的时候，甚至尝试过讲HTML/CSS覆盖在Canvas上做UI部分的方案，但是混合的性能并不好，并且还有各种兼容问题。综上，所以实现脏矩形渲染是非常必要的。&lt;/p&gt;

</description>
        <pubDate>Sat, 21 May 2016 00:00:00 +0000</pubDate>
        <link>https://idom.me/articles/841.html</link>
        <guid isPermaLink="true">https://idom.me/articles/841.html</guid>
        
        <category>HTML5</category>
        
        <category>图形渲染</category>
        
        
      </item>
    
  </channel>
</rss>
