<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Found Pan Tiger</title>
        <link>https://paragraph.com/@aliez</link>
        <description>undefined</description>
        <lastBuildDate>Mon, 07 Sep 2026 07:26:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Found Pan Tiger</title>
            <url>https://storage.googleapis.com/papyrus_images/34a83ec026eda7dec363adb8e5f1f17b0a1408b246ceafcaa17a13e15318163d.png</url>
            <link>https://paragraph.com/@aliez</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[介绍 ERC721A：一种改进的ERC721实现]]></title>
            <link>https://paragraph.com/@aliez/erc721a-erc721</link>
            <guid>ZBQ6qzNe1vKeMfdBiMPq</guid>
            <pubDate>Fri, 07 Jan 2022 07:06:20 GMT</pubDate>
            <description><![CDATA[本文翻译自： https://www.azuki.com/erc721a简介ERC721A 是 IERC721 的一种实现，在单笔交易中 mint 多个 NFT 可显著节省 gas。Azuki 合约将使 mint 多个 NFT 的成本与 mint 单个 NFT 的成本基本相同。我们与外部安全顾问 (Caribou) 合作，以确保我们的方法是可行的。我们鼓励更广泛的社区采用这种实施方式来为他们的用户节省 gas 成本。起因几个月来，以太坊的 Gas 价格一直居高不下，开发社区需要适应。当流行的 NFT 项目开始出现时，gas 价格飙升，导致整个生态系统支付数百万的 gas 费用来进行交易。在 Azuki，我们正在与我们的社区一起为 Metaverse 建立一个品牌。开发团队的重点是优化我们的合约，并使我们的社区在 mint 时尽可能少地花费 gas 费用。 我们没有使用 OpenZeppelin 流行的 IERC721 和 IERC721Enumerable 默认实现，而是编写了我们的版本（在本文的其余部分，我们将其称为 ERC721A）并很高兴地宣布，Azuki 合约的确能够实现...]]></description>
            <content:encoded><![CDATA[<p>本文翻译自：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.azuki.com/erc721a">https://www.azuki.com/erc721a</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">简介</h2><ul><li><p>ERC721A 是 IERC721 的一种实现，在单笔交易中 mint 多个 NFT 可显著节省 gas。</p></li><li><p>Azuki 合约将使 mint 多个 NFT 的成本与 mint 单个 NFT 的成本基本相同。</p></li><li><p>我们与外部安全顾问 (Caribou) 合作，以确保我们的方法是可行的。</p></li><li><p>我们鼓励更广泛的社区采用这种实施方式来为他们的用户节省 gas 成本。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">起因</h2><p>几个月来，以太坊的 Gas 价格一直居高不下，开发社区需要适应。当流行的 NFT 项目开始出现时，gas 价格飙升，导致整个生态系统支付数百万的 gas 费用来进行交易。在 Azuki，我们正在与我们的社区一起为 Metaverse 建立一个品牌。开发团队的重点是优化我们的合约，并使我们的社区在 mint 时尽可能少地花费 gas 费用。</p><p>我们没有使用 OpenZeppelin 流行的 IERC721 和 IERC721Enumerable 默认实现，而是编写了我们的版本（在本文的其余部分，我们将其称为 ERC721A）并很高兴地宣布，Azuki 合约的确能够实现 mint 多个 NFT 与 mint 单个 NFT 的 gas 成本相同。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">性能比较</h2><p>我们比较了 mint 的 gas 成本和价格，比较了 OpenZeppelin 的 ERC721Enumerable 与 ERC721A。在我们的比较中，使用了相同的应用程序逻辑，唯一的区别是调用了 _safeMint 函数。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3948ae92950a1eb187cf8a18b3a83839f1020ce3e30c2eefbc6d0dd0a143fcc5.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>假设成本时 300 gwei 和 3500 美元/ETH。这些只是估计值，很难预测 mint 时的 gas 和 eth 价格（目前在撰写本文时为 120 gwei）。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/61740bc471dc8a3113c1ddc6bf8136ee9fb175d6e9d8d1b39193f0edc8cc9015.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">这是如何实现的？</h2><p>我们在下面详细介绍了我们所做的三个主要优化。虽然我们试图让它尽可能清楚 - 这篇文章的其余部分确实假设您已经了解 Solidity 和 OpenZeppelin 的 ERC721 / ERC721Enumerable 实现。</p><h3 id="h-1-openzeppelin-oz-erc721enumerable" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">优化 1 - 从 OpenZeppelin (OZ) ERC721Enumerable 中删除重复存储</h3><p>IERC721Enumerable 的广泛使用的 OZ 实现包括每个令牌元数据的冗余存储。这种非规范化方法以写入函数的显着成本优化读取函数，这并不理想，因为用户不太可能为读取函数付费。此外，我们的令牌从 0 开始按顺序编号这一事实让我们从基本实现中删除了一些冗余存储。我们强烈建议所有新发布的产品如果正在寻找大赢家，请仔细检查此文件。</p><h3 id="h-2-nft" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">优化 2 - 每批铸币请求更新一次所有者的余额，而不是每个铸币的 NFT</h3><p>假设 Alice 有 2 个代币并想再购买 5 个。在 Solidity 中，更新存储值需要消耗 gas。因此，如果我们在存储中跟踪 Alice 拥有多少代币，通过一次更新将 Alice 的持有量从 2 直接更新为 7 会更便宜，而不是将该值更新 5 次（每个额外的代币一次，从 2 到 3、3到 4 等）。</p><p>虽然这是一个相对简单的概念，但 NFT 领域中的绝大多数批量 mint 还没有采用这一点，因为 OZ 默认实现不包括批量 mint API，并且很容易从货架上获取现有解决方案而不对其进行调整.我们强烈建议所有支持批量 mint 的项目都考虑这个技巧。</p><h3 id="h-3-mint-minted-nft" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">优化 3 - 每个批处理 mint 请求更新一次所有者数据，而不是每个 minted NFT</h3><p>这在本质上与优化 2 相似。假设 Alice 想购买 3 个代币 - 代币 #100、#101 和 #102。与其将 Alice 保存为所有者 3 次（每次都花费我们gas），我们可以改为仅保存所有者值一次，在语义上暗示 Alice 拥有所有 3 个令牌。</p><p>如何？假设 Alice 铸造了代币 #100、#101 和 #102，而 Bob 铸造了代币 #103 和 #104。内部所有者状态如下所示：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/21fdefe5f949186ccf4f62ba1f50a51738208c2cfaf3a77c5eb2cf2c7a563866.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>这里的关键是，如果我们想查看谁拥有 #102，我们实际上不需要将 Alice 显式设置为 #102 的显式所有者来执行此操作。我们可以更改 ownerOf 函数来执行以下操作：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9e8abafab98093ea40ef2f1cfa1a2d5dd5fa36fc40ee8a761a3b61274faea2bf.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>如果我们将其实现更改为递减，直到找到明确的所有者集，ownerOf 仍能按预期工作。</p><p>虽然这些延迟的所有者写入可能仍会在代币生命周期的后期发生，但我们仍然预计从整体上节省大量的净费用，因为这会减少 mint 时的 gas 花费，从而降低 mint 时整个生态系统的集中 gas 峰值的严重性。这种优化涉及一些额外的逻辑，尤其是在传输方面，但超出了本博客的范围。合同将在 mint 时间之前公开，开发人员将很乐意回答任何问题并进行讨论！</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">写在最后</h2><p>我们相信，铸造多个 NFT 的 Gas 成本低于我们在该领域所见的任何费用。我们的外部安全顾问 (Caribou) 已审查了合同，以确保我们的方法是合理的，以便用户自信地享受这些天然气节省。所有项目都应努力最大程度地减少 gas 负担，并尽可能为其社区迈向 O(1)。我们鼓励该领域的其他项目采用 ERC721A 标准进行任何公开销售，允许在一次交易中铸造多个 NFT。有关合同的任何问题，无论是关于代码还是关于为您的项目实施它，请通过 Twitter 私信联系 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/locationtba">@locationtba</a> 或 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/2pmflow">@2pmflow</a>。</p><p>Azuki 将于太平洋标准时间 1 月 12 日上午 10:00 开售，到时候见。</p>]]></content:encoded>
            <author>aliez@newsletter.paragraph.com (Found Pan Tiger)</author>
        </item>
        <item>
            <title><![CDATA[WASM 输入与输出]]></title>
            <link>https://paragraph.com/@aliez/wasm</link>
            <guid>xHxnSBkUGDJVuGlK3gcK</guid>
            <pubDate>Thu, 21 Oct 2021 03:30:36 GMT</pubDate>
            <description><![CDATA[WASM 简介WASM（WebAssembly）是一种用于堆栈式虚拟机的二进制指令格式。它是是一种文件格式标准，不是特定的虚拟机实现，因此有各种 compiler、interpreter 以及 runtime，如wastime、Wasmer、V8、 Lucet、WAMR、wasm3。 WASM 在设计之初，考虑了以下几点：🔗高效和快速：体积小，加载速度快。可以流式加载，边下载边初始化，减少时间和内存占用。理论上可以达到原生的运行速度。安全：WASM 描述了一个内存安全的沙箱执行环境，甚至可以在现有的 JavaScript 虚拟机中实现。开放可调试：可以翻译为可读的文本格式.wat（可以理解为 WASM 平台的汇编语言），配合 source map 方便调试。Web 标准：模块能够传入和传出 JavaScript 上下文，并通过 JavaScript 访问 Web API 来访问浏览器功能。同时也支持非浏览器环境中运行。但也有以下限制：基础数据类型只有 i32、i64、f32、f64。高级数据结构（如：字符串）每种语言需要自己实现。🔗只能进行同步计算，不支持异步函数。对于 Go...]]></description>
            <content:encoded><![CDATA[<h2 id="h-wasm" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">WASM 简介</h2><p>WASM（WebAssembly）是一种用于堆栈式虚拟机的二进制指令格式。它是是一种文件格式标准，不是特定的虚拟机实现，因此有各种 compiler、interpreter 以及 runtime，如<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/bytecodealliance/wasmtime">wastime</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wasmer.io/">Wasmer</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://v8.dev/docs/wasm-compilation-pipeline">V8</a>、 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/bytecodealliance/lucet/">Lucet</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/bytecodealliance/wasm-micro-runtime">WAMR</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/wasm3/wasm3/">wasm3</a>。</p><p>WASM 在设计之初，考虑了以下几点：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://webassembly.org/">🔗</a></p><ul><li><p><strong>高效和快速</strong>：体积小，加载速度快。可以流式加载，边下载边初始化，减少时间和内存占用。理论上可以达到原生的运行速度。</p></li><li><p><strong>安全</strong>：WASM 描述了一个内存安全的沙箱执行环境，甚至可以在现有的 JavaScript 虚拟机中实现。</p></li><li><p><strong>开放可调试</strong>：可以翻译为可读的文本格式<code>.wat</code>（可以理解为 WASM 平台的汇编语言），配合 source map 方便调试。</p></li><li><p><strong>Web 标准</strong>：模块能够传入和传出 JavaScript 上下文，并通过 JavaScript 访问 Web API 来访问浏览器功能。同时也<strong>支持非浏览器环境中运行</strong>。</p></li></ul><p>但也有以下限制：</p><ul><li><p>基础数据类型只有 i32、i64、f32、f64。高级数据结构（如：字符串）每种语言需要自己实现。<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.assemblyscript.org/memory.html#internals">🔗</a></p></li><li><p>只能进行同步计算，不支持异步函数。</p></li><li><p>对于 Go、Python 这种带运行时的语言编译到 WASM，需要将运行时一起打包，生成的文件体积较大。</p></li></ul><p>接下来我们将使用 Node.js（V8）作为 runtime 语言，AssemblyScript 作为编译到 WASM 的语言，来做点实验。</p><h2 id="h-assemblyscript" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">AssemblyScript</h2><blockquote><p>A language made for WebAssembly.</p></blockquote><p>对于很多在 WASM 诞生前就已经存在的语言，WASM 就像 x86、ARM64 一样，只是一种新的 platform target。</p><p>而 AssemblyScript 是一个为 WASM 而生的语言，目前只能编译到 WASM。它的语法是一种 TypeScript 的变体，但实际写起来时，有时却要像写 C 语言那样思考。</p><p>AssemblyScript 项目搭建请参考<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.assemblyscript.org/quick-start.html">Quick Start</a>。</p><h2 id="h-ab" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">a+b</h2><p>我们来写一个简单的 a+b，为什么不是 hello world？因为字符串在 WASM 里是高级数据结构。我们先从最简单的 i32 开始：</p><pre data-type="codeBlock" text="// sum.ts
export function sum(a: i32, b: i32): i32 {
  return a + b;
}
"><code><span class="hljs-comment">// sum.ts</span>
<span class="hljs-function"><span class="hljs-keyword">export</span> function <span class="hljs-title">sum</span><span class="hljs-params">(a: i32, b: i32)</span>: i32 {</span>
  <span class="hljs-keyword">return</span> a + b;
}
</code></pre><p>编译后得到一个二进制文件<code>sum.wasm</code>，和一个文本文件<code>sum.wat</code>。两者可以使用<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/WebAssembly/wabt">wabt</a>互相转换。我们以文本格式为例：</p><pre data-type="codeBlock" text="; sum.wat
(module
  (type $t0 (func (param i32 i32) (result i32)))
  (func $sum (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
    local.get $p0
    local.get $p1
    i32.add)
  (memory $memory 0)
  (export &quot;sum&quot; (func $sum))
  (export &quot;memory&quot; (memory 0)))
"><code>; sum.wat
<span class="hljs-punctuation">(</span><span class="hljs-keyword">module</span>
  <span class="hljs-punctuation">(</span><span class="hljs-keyword">type</span> <span class="hljs-variable">$t0</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">func</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">param</span> <span class="hljs-type">i32</span> <span class="hljs-type">i32</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">result</span> <span class="hljs-type">i32</span><span class="hljs-punctuation">)))</span>
  <span class="hljs-punctuation">(</span><span class="hljs-keyword">func</span> <span class="hljs-title function_">$sum</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">type</span> <span class="hljs-variable">$t0</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">param</span> <span class="hljs-variable">$p0</span> <span class="hljs-type">i32</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">param</span> <span class="hljs-variable">$p1</span> <span class="hljs-type">i32</span><span class="hljs-punctuation">)</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">result</span> <span class="hljs-type">i32</span><span class="hljs-punctuation">)</span>
    <span class="hljs-keyword">local.get</span> <span class="hljs-variable">$p0</span>
    <span class="hljs-keyword">local.get</span> <span class="hljs-variable">$p1</span>
    <span class="hljs-keyword">i32.add</span><span class="hljs-punctuation">)</span>
  <span class="hljs-punctuation">(</span><span class="hljs-keyword">memory</span> <span class="hljs-variable">$memory</span> <span class="hljs-number">0</span><span class="hljs-punctuation">)</span>
  <span class="hljs-punctuation">(</span><span class="hljs-keyword">export</span> <span class="hljs-string">"sum"</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">func</span> <span class="hljs-title function_">$sum</span><span class="hljs-punctuation">))</span>
  <span class="hljs-punctuation">(</span><span class="hljs-keyword">export</span> <span class="hljs-string">"memory"</span> <span class="hljs-punctuation">(</span><span class="hljs-keyword">memory</span> <span class="hljs-number">0</span><span class="hljs-punctuation">)))</span>
</code></pre><p>如果学过汇编，不了解 WAT 语法也可以看懂个大概。值得注意的是<code>export &quot;memory&quot;</code>，这是 WSAM 把内存暴露给 runtime，后面的例子会用到。</p><p>接下来在 Node.js 中调用：</p><pre data-type="codeBlock" text="import fs from &quot;fs&quot;;

const file = await fs.promises.readFile(&quot;./sum.wasm&quot;);
const module = new WebAssembly.Module(file);
const instance = new WebAssembly.Instance(module);

console.log((instance.exports.sum as Function)(1, 2));
// stdout: 3
"><code><span class="hljs-keyword">import</span> <span class="hljs-title">fs</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"fs"</span>;

const file <span class="hljs-operator">=</span> await fs.promises.readFile(<span class="hljs-string">"./sum.wasm"</span>);
const module <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> WebAssembly.Module(file);
const instance <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> WebAssembly.Instance(module);

console.log((instance.exports.sum <span class="hljs-keyword">as</span> Function)(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>));
<span class="hljs-comment">// stdout: 3</span>
</code></pre><p>只需几行代码就完成了调用，并输出了预期的结果。</p><h2 id="h-hello-world" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">&apos;hello, world!&apos;</h2><p>来看看字符串是如何在 WASM 与 runtime 之间传递的。</p><pre data-type="codeBlock" text="// hello-world.ts
export function hello_world(): string {
  return `hello, world!`;
}
"><code><span class="hljs-comment">// hello-world.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">hello_world</span>(<span class="hljs-params"></span>): string {
  <span class="hljs-keyword">return</span> <span class="hljs-string">`hello, world!`</span>;
}
</code></pre><p>编译后得到：</p><pre data-type="codeBlock" text="; hello-world.wat
(module
  (type $t0 (func (result i32)))
  (func $hello_world (type $t0) (result i32)
    i32.const 1056)
  (memory $memory 1)
  (export &quot;hello_world&quot; (func $hello_world))
  (export &quot;memory&quot; (memory 0))
  (data $d0 (i32.const 1036) &quot;,&quot;)
  (data $d1 (i32.const 1048) &quot;\01\00\00\00\1a\00\00\00h\00e\00l\00l\00o\00,\00 \00w\00o\00r\00l\00d\00!&quot;))
"><code>; hello<span class="hljs-operator">-</span>world.wat
(module
  (type <span class="hljs-variable">$t0</span> (<span class="hljs-keyword">func</span> (result i32)))
  (<span class="hljs-keyword">func</span> <span class="hljs-variable">$hello_world</span> (type <span class="hljs-variable">$t0</span>) (result i32)
    i32.const <span class="hljs-number">1056</span>)
  (memory <span class="hljs-variable">$memory</span> <span class="hljs-number">1</span>)
  (export <span class="hljs-string">"hello_world"</span> (<span class="hljs-keyword">func</span> <span class="hljs-variable">$hello_world</span>))
  (export <span class="hljs-string">"memory"</span> (memory <span class="hljs-number">0</span>))
  (data <span class="hljs-variable">$d0</span> (i32.const <span class="hljs-number">1036</span>) <span class="hljs-string">","</span>)
  (data <span class="hljs-variable">$d1</span> (i32.const <span class="hljs-number">1048</span>) <span class="hljs-string">"<span class="hljs-subst">\0</span>1<span class="hljs-subst">\0</span>0<span class="hljs-subst">\0</span>0<span class="hljs-subst">\0</span>0\1a<span class="hljs-subst">\0</span>0<span class="hljs-subst">\0</span>0<span class="hljs-subst">\0</span>0h<span class="hljs-subst">\0</span>0e<span class="hljs-subst">\0</span>0l<span class="hljs-subst">\0</span>0l<span class="hljs-subst">\0</span>0o<span class="hljs-subst">\0</span>0,<span class="hljs-subst">\0</span>0 <span class="hljs-subst">\0</span>0w<span class="hljs-subst">\0</span>0o<span class="hljs-subst">\0</span>0r<span class="hljs-subst">\0</span>0l<span class="hljs-subst">\0</span>0d<span class="hljs-subst">\0</span>0!"</span>))
</code></pre><p>在 Node.js 中调用：</p><pre data-type="codeBlock" text="import fs from &quot;fs&quot;;

const file = await fs.promises.readFile(&quot;./hello-world.wasm&quot;);
const module = new WebAssembly.Module(file);
const instance = new WebAssembly.Instance(module);

console.log((instance.exports.hello_world as Function)());
// stdout: 1056
"><code><span class="hljs-keyword">import</span> <span class="hljs-title">fs</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"fs"</span>;

const file <span class="hljs-operator">=</span> await fs.promises.readFile(<span class="hljs-string">"./hello-world.wasm"</span>);
const module <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> WebAssembly.Module(file);
const instance <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> WebAssembly.Instance(module);

console.log((instance.exports.hello_world <span class="hljs-keyword">as</span> Function)());
<span class="hljs-comment">// stdout: 1056</span>
</code></pre><p>结果不是&quot;hello, world!&quot;，而是一个数字，发生什么事了？</p><p>这个数字其实是一个指针的地址，它指向了一个字符串。通过<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.assemblyscript.org/memory.html#string-layout">string-layout</a>这篇文档，我们可以知道 AssemblyScript 的字符串是如何在内存中排布的，因此我们可以编写代码把字符串取出来。</p><pre data-type="codeBlock" text="// ...
const offset = (instance.exports.hello_world as Function)();
const memory = instance.exports.memory as WebAssembly.Memory;
const buffer = Buffer.from(memory.buffer);
const len = buffer.readUInt32LE(offset - 4);

console.log(buffer.slice(offset, offset + len).toString());
// stdout: hello, world!
"><code><span class="hljs-comment">// ...</span>
const offset <span class="hljs-operator">=</span> (instance.exports.hello_world <span class="hljs-keyword">as</span> Function)();
const <span class="hljs-keyword">memory</span> <span class="hljs-operator">=</span> instance.exports.memory <span class="hljs-keyword">as</span> WebAssembly.Memory;
const buffer <span class="hljs-operator">=</span> Buffer.from(<span class="hljs-keyword">memory</span>.buffer);
const len <span class="hljs-operator">=</span> buffer.readUInt32LE(offset <span class="hljs-operator">-</span> <span class="hljs-number">4</span>);

console.log(buffer.slice(offset, offset <span class="hljs-operator">+</span> len).toString());
<span class="hljs-comment">// stdout: hello, world!</span>
</code></pre><p>WASM 使用的是<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions">little endian byte order</a>，因此我们用<code>readUInt32LE</code>而不是<code>readUInt32BE</code>。</p><h2 id="h-hello-dollarstr" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">`hello, ${str}!`</h2><p>我们已经学会把字符串从 WASM 传到 runtime 中，接下来我们学习反向操作。</p><pre data-type="codeBlock" text="// hello.ts
export function hello(str: string): string {
  return `hello, ${str}!`;
}
"><code><span class="hljs-comment">// hello.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">hello</span>(<span class="hljs-params">str: <span class="hljs-built_in">string</span></span>): <span class="hljs-built_in">string</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-string">`hello, <span class="hljs-subst">${str}</span>!`</span>;
}
</code></pre><p>使用编译参数：</p><pre data-type="codeBlock" text="asc hello.ts --use abort= --target release
"><code>asc hello.ts <span class="hljs-operator">-</span><span class="hljs-operator">-</span>use abort<span class="hljs-operator">=</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>target release
</code></pre><p>我们只改了几个字符，但这次编译出的 hello.wat 已经有 400+行。</p><p>在前两个例子中，内存是静态的，而在这个例子里我们进行了字符串拼接，就涉及到了内存的动态管理。因此 AssemblyScript 需要把内存回收运行时打包进 WASM，就增加了文件体积。</p><pre data-type="codeBlock" text="// ...
const memory = instance.exports.memory as WebAssembly.Memory;
const buffer = Buffer.from(memory.buffer);
const str = &quot;wasm&quot;;
const ptr = 1024;
buffer.writeUInt32LE(str.length, ptr - 4);
Buffer.from(str).copy(buffer, ptr);
const offset = (instance.exports.hello as Function)(ptr);
const len = buffer.readUInt32LE(offset - 4);

console.log(buffer.slice(offset, offset + len).toString());
// stdout: hello, wasm!
"><code><span class="hljs-comment">// ...</span>
const <span class="hljs-keyword">memory</span> <span class="hljs-operator">=</span> instance.exports.memory <span class="hljs-keyword">as</span> WebAssembly.Memory;
const buffer <span class="hljs-operator">=</span> Buffer.from(<span class="hljs-keyword">memory</span>.buffer);
const str <span class="hljs-operator">=</span> <span class="hljs-string">"wasm"</span>;
const ptr <span class="hljs-operator">=</span> <span class="hljs-number">1024</span>;
buffer.writeUInt32LE(str.<span class="hljs-built_in">length</span>, ptr <span class="hljs-operator">-</span> <span class="hljs-number">4</span>);
Buffer.from(str).copy(buffer, ptr);
const offset <span class="hljs-operator">=</span> (instance.exports.hello <span class="hljs-keyword">as</span> Function)(ptr);
const len <span class="hljs-operator">=</span> buffer.readUInt32LE(offset <span class="hljs-operator">-</span> <span class="hljs-number">4</span>);

console.log(buffer.slice(offset, offset <span class="hljs-operator">+</span> len).toString());
<span class="hljs-comment">// stdout: hello, wasm!</span>
</code></pre><p>这里的<code>ptr = 1024</code>是一个危险的做法，这个例子里我们假定 WASM 不会使用第 1024 个字节附近的内存，实际使用中不能这么写，这样可能会覆盖掉 WASM 所需要的数据，使 WASM 运行异常。</p><p>AssemblyScript 提供了<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.assemblyscript.org/loader.html#loader">Loader</a>，封装了这种高级数据结构传递的方法；Rust 也提供了<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/rustwasm/wasm-bindgen">wasm-bingen</a>，可以生成<code>.js</code>和<code>.d.ts</code>文件，在 js 代码中直接 import 就可以调用 Rust 编写的 WASM 模块中的函数。</p><p>到这里，相信你对如何在 WASM 与 runtime 之间传递数据已经有了初步了解。</p>]]></content:encoded>
            <author>aliez@newsletter.paragraph.com (Found Pan Tiger)</author>
        </item>
        <item>
            <title><![CDATA[用过的显示器]]></title>
            <link>https://paragraph.com/@aliez/nx1QW4wmITJdydt4QuDO</link>
            <guid>nx1QW4wmITJdydt4QuDO</guid>
            <pubDate>Thu, 21 Oct 2021 03:30:17 GMT</pubDate>
            <description><![CDATA[用过的显示器DELL P2415Q23.5 寸 4K IPS 屏 ，2015-08 购买，3000 元。 当时 4K 显示器刚开始普及，可选的型号不多。还专门为这个显示器配了一个台式机，i3-2130 处理器，R9 Nano 显卡，可以畅玩 4K 全高特效 DotA2。 后来公司采购显示器我就推荐的这款型号，办公用非常不错。 这台显示器后来卖给公司了。ELSA E32B700BD31.5 寸 4K VA 屏 ，2017-06 购买，2199 元。 是个杂牌显示器，但性价比非常高，VA 屏色彩艳丽。当时合租的室友看到后也买了一台。 这台显示器卖给同事了，但他使用一年后就坏了。LM315WR1 SSA1 B131.5 寸 4K IPS 屏 ，2018-03 购买，显示屏+驱动板 2180 元。 这是 LG 32UD99 同款面板，不带外壳，使用一块亚克力背板链接屏幕与驱动板。 在 macOS 下驱动有点问题，无法输出 4K，需要在 /Library/Displays/Contents/Resources/Overrides/ 目录下放一个配置文件才能正常使用。 这台显示器也卖给同事了...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">用过的显示器</h2><h2 id="h-dell-p2415q" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">DELL P2415Q</h2><p>23.5 寸 4K IPS 屏 ，2015-08 购买，3000 元。</p><p>当时 4K 显示器刚开始普及，可选的型号不多。还专门为这个显示器配了一个台式机，i3-2130 处理器，R9 Nano 显卡，可以畅玩 4K 全高特效 DotA2。</p><p>后来公司采购显示器我就推荐的这款型号，办公用非常不错。</p><p>这台显示器后来卖给公司了。</p><h2 id="h-elsa-e32b700bd" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ELSA E32B700BD</h2><p>31.5 寸 4K VA 屏 ，2017-06 购买，2199 元。</p><p>是个杂牌显示器，但性价比非常高，VA 屏色彩艳丽。当时合租的室友看到后也买了一台。</p><p>这台显示器卖给同事了，但他使用一年后就坏了。</p><h2 id="h-lm315wr1-ssa1-b1" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">LM315WR1 SSA1 B1</h2><p>31.5 寸 4K IPS 屏 ，2018-03 购买，显示屏+驱动板 2180 元。</p><p>这是 LG 32UD99 同款面板，不带外壳，使用一块亚克力背板链接屏幕与驱动板。</p><p>在 macOS 下驱动有点问题，无法输出 4K，需要在 <code>/Library/Displays/Contents/Resources/Overrides/</code> 目录下放一个<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gist.github.com/renzholy/fee4d3cd85c5b745f5d64fff81099ec7">配置文件</a>才能正常使用。</p><p>这台显示器也卖给同事了。</p><h2 id="h-fight-k27md5kal" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">FIGHT K27MD5KAL</h2><p>27 寸 5K IPS 屏 ，2019-03 购买，2999 元。</p><p>用了 5 年的 4K 显示器了，想体验一下更高的分辨率。</p><p>这款显示器采用的是 LG UltraFine 5K 同款面板，但是需要两根 DP 线才能驱动 5K 分辨率。使用一段时间后发现显示器有残影，从白色界面切换到灰色界面时最为明显，过半分钟左右消失。除了这个问题之外其他方面都还挺不错的。</p><p>这台目前在家里使用。</p><h2 id="h-geekon-28" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Geekon 28</h2><p>28 寸 4.5K IPS 屏 ，2021-04 购买，6100 元。</p><p>这款显示器采用的是 Surface Studio 2 同款屏幕，分辨率 4500x3000，屏幕比例 3:2。</p><p>冲这奇葩的分辨率而购买的，买回来刚开箱感觉非常惊艳，但点亮后就发现屏幕四周亮度衰减非常明显。最致命的是屏幕中间纵向有几个像素不见了，老板说是驱动板的 bug，在联系厂家修复。我去一家制作驱动板的店铺咨询，给到的答复是目前无解。</p><p>于是我用了一天就退货了。</p><h2 id="h-pro-display-xdr" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Pro Display XDR</h2><p>32 寸 6K IPS 屏幕，2021-04 购买，纳米纹理玻璃款加支架 55798 元。</p><p>购买后等了一个多月才送到，怀疑是现做现卖。</p><p>在 Youtube 上找了几个 4K HDR 视频看。非全屏状态下，使用 Chrome 时，视频那个区域的亮度和其他部分不一样；使用 Safari 时，整个屏幕的亮度随视频内容而变化。明暗对比强烈，色彩鲜艳。</p><p>播放本地视频时，iina、VLC、Movist、Optimus Player 均无法正常 HDR。按照网上的说法，我使用<code>ffprobe -v error -show_streams -select_streams v:0 -of json -i video.mkv</code>命令查看视频文件的色彩空间是<code>BT.2020</code>，说明确实是 HDR 视频文件，不知问题出在哪里。我又用<code>ffmpeg -i video.mkv -c copy -tag:v hvc1 video.mp4</code>把它转成 MP4 封装，使用 Quick Time 播放，仍然看不出有 HDR 的效果。</p><h2 id="h-lg-oled48cxpca" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">LG OLED48CXPCA</h2><p>48 寸 4K 120Hz OLED 屏幕，2021-08 购买于闲鱼，7500 元。</p><p>买电视当显示器用，性价比非常高，几乎占满整个桌面，使用体验非常震撼。美中不足的是只有 HDMI2.1 接口，目前显卡太贵不想买，我手中的设备只能支持到 4K 60Hz HDR 或 4K 120Hz 420，要么损失帧率，要么损失色彩。</p><p>即便如此表现依然非常亮眼，屏幕表现不必多说，就讲讲其他的体验。电视开机很快，小于 10s；WebOS 很不错，没有广告，不卡顿；支持 AMD Freesync Premium 可变刷新率；作为一款电视来说延迟很低，特别是在 120Hz 刷新率下，但 60Hz 下打 CS 还是不太行的；使用中还是会有意识避免长时间显示同一画面，从而降低烧屏概率，有一点不舒服。</p>]]></content:encoded>
            <author>aliez@newsletter.paragraph.com (Found Pan Tiger)</author>
        </item>
        <item>
            <title><![CDATA[使用 React 后端渲染 SVG 图片]]></title>
            <link>https://paragraph.com/@aliez/react-svg</link>
            <guid>SDWPUgf1BhDbyMYkfrOa</guid>
            <pubDate>Thu, 21 Oct 2021 03:27:55 GMT</pubDate>
            <description><![CDATA[这篇文章教你使用 Figma 作为设计工具，使用 React 作为模版渲染工具，使用 Next.js 作为 SSR，渲染出一个 SVG 图片。最近参与制作了即刻里的一个小功能“我的持仓”。其前端主要分为两部分：一部分是即刻个人主页下的“小卡片”，另一部分是点击小卡片后跳转到的网页。我们主要来讲讲这个小卡片是如何实现的。小卡片实际上是一张 SVG 图片，放在整个卡片列表中的一个 <img /> 标签里为什么不以 SVG DOM 的方式渲染？这样做的话，卡片列表（上图黄框部分）和卡片内容（上图红框部分）代码要放在同一个项目中，“我的持仓”业务逻辑和即刻的业务逻辑就耦合了。为什么不用 PNG 图片？因为图片中含有文字，PNG 图片无法适配不同手机的不同字体。Figma 导出 SVG导出时注意不要勾选 Outline Text，该选项会把 text 变成 path，也就是把字体当作矢量图来画。但是在我们的渲染中需要 text 作为变量，因此不要勾选。React 渲染 SVGReact 本身就支持 SVG 渲染，把刚才导出的 svg 代码复制粘贴成为一个 React Componentex...]]></description>
            <content:encoded><![CDATA[<p>这篇文章教你使用 Figma 作为设计工具，使用 React 作为模版渲染工具，使用 Next.js 作为 SSR，渲染出一个 SVG 图片。</p><hr><p>最近参与制作了即刻里的一个小功能“我的持仓”。其前端主要分为两部分：一部分是即刻个人主页下的“小卡片”，另一部分是点击小卡片后跳转到的网页。我们主要来讲讲这个小卡片是如何实现的。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/058b6056c7751ad1d16f6eec7dc7afc8830583a9b1238ecd12cc30d7df3d9e00.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><ul><li><p>小卡片实际上是一张 SVG 图片，放在整个卡片列表中的一个 <code>&lt;img /&gt;</code> 标签里</p></li><li><p>为什么不以 SVG DOM 的方式渲染？这样做的话，卡片列表（上图黄框部分）和卡片内容（上图红框部分）代码要放在同一个项目中，“我的持仓”业务逻辑和即刻的业务逻辑就耦合了。</p></li><li><p>为什么不用 PNG 图片？因为图片中含有文字，PNG 图片无法适配不同手机的不同字体。</p></li></ul><h2 id="h-figma-svg" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Figma 导出 SVG</h2><p>导出时注意不要勾选 <code>Outline Text</code>，该选项会把 text 变成 path，也就是把字体当作矢量图来画。但是在我们的渲染中需要 text 作为变量，因此不要勾选。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/afc21cc349f5ef34c4c6ba4d0be613b5cac00e8e91dc6cf36c22485e263eec70.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-react-svg" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">React 渲染 SVG</h2><p>React 本身就支持 SVG 渲染，把刚才导出的 svg 代码复制粘贴成为一个 React Component</p><pre data-type="codeBlock" text="export const SvgBox = () =&gt; (
  &lt;svg
    width=&quot;100&quot;
    height=&quot;100&quot;
    viewBox=&quot;0 0 100 100&quot;
    xmlns=&quot;http://www.w3.org/2000/svg&quot;
  &gt;
    ...
  &lt;/svg&gt;
);
"><code>export const SvgBox <span class="hljs-operator">=</span> () <span class="hljs-operator">=</span><span class="hljs-operator">></span> (
  <span class="hljs-operator">&#x3C;</span>svg
    width<span class="hljs-operator">=</span><span class="hljs-string">"100"</span>
    height<span class="hljs-operator">=</span><span class="hljs-string">"100"</span>
    viewBox<span class="hljs-operator">=</span><span class="hljs-string">"0 0 100 100"</span>
    xmlns<span class="hljs-operator">=</span><span class="hljs-string">"http://www.w3.org/2000/svg"</span>
  <span class="hljs-operator">></span>
    ...
  <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>svg<span class="hljs-operator">></span>
);
</code></pre><h2 id="h-nextjs-ssr" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Next.js SSR</h2><p>使用 Next.js 的约定式路由，新建一个页面文件，使用 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup">renderToStaticMarkup</a> 和 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering">getServerSideProps</a> 进行服务端渲染。请求这个页面时，返回的是 SVG，而不是 HTML。</p><pre data-type="codeBlock" text="import type { GetServerSideProps } from &quot;next&quot;;
import { renderToStaticMarkup } from &quot;react-dom/server&quot;;

export default function SvgPage() {
  return null;
}

export const getServerSideProps: GetServerSideProps = async ({ res }) =&gt; {
  res.setHeader(&quot;Content-Type&quot;, &quot;image/svg+xml&quot;);
  res.write(renderToStaticMarkup(&lt;SvgBox /&gt;));
  res.end();
  return {
    props: {},
  };
};
"><code><span class="hljs-keyword">import</span> <span class="hljs-title"><span class="hljs-keyword">type</span></span> { <span class="hljs-title">GetServerSideProps</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">renderToStaticMarkup</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react-dom/server"</span>;

export default <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SvgPage</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> null;
}

export const getServerSideProps: GetServerSideProps <span class="hljs-operator">=</span> async ({ res }) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  res.setHeader(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"image/svg+xml"</span>);
  res.write(renderToStaticMarkup(<span class="hljs-operator">&#x3C;</span>SvgBox <span class="hljs-operator">/</span><span class="hljs-operator">></span>));
  res.end();
  <span class="hljs-keyword">return</span> {
    props: {},
  };
};
</code></pre><p>之后所需要做的事情就是根据业务逻辑给 <code>&lt;SvgBox /&gt;</code> 组件传入变量，就像写普通 React 组件一样。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">嵌入图片</h2><p>值得注意的是，为了在 Android 系统上显示 Apple Emoji，小卡片第二行行首的 emoji 不是字符，而是 PNG 图片。</p><h3 id="h-base64-encode" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">base64 encode</h3><p>在 <code>&lt;img /&gt;</code> 标签里显示的 svg 不能再请求外部资源，所以我们需要把 PNG base64 encode 后作为 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs">Data URL</a> 传入 SVG 的 <code>&lt;image /&gt;</code> 标签中，而不能只传一个 url。</p><h3 id="h-avif-webp" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">AVIF 和 WebP</h3><p>PNG 图片 base64 后体积太大了，因此我为每张 emoji PNG 都准备了对应的 AVIF 和 WebP 版本，根据 <code>req.headers.accept</code> 来给 SVG 嵌入不同格式的图片</p>]]></content:encoded>
            <author>aliez@newsletter.paragraph.com (Found Pan Tiger)</author>
        </item>
    </channel>
</rss>