@@ -243,11 +243,11 @@ private:
243
243
244
244
## 栈机
245
245
246
- 要执行一个复杂的嵌套表达式,你从最内层的子表达式开始。计算完的内层表达式的结果,就变成了包含它们的外层表达式的参数,以此类推,直到整个表达式计算完毕 。
246
+ 要执行一个复杂的嵌套表达式,你从最内层的子表达式开始。计算完的内层表达式的结果,就将结果作为包含它们的外层表达式的参数传给外层表达式进行计算,以此类推直到整个表达式计算完毕 。
247
247
248
- 解释器模式将它显式建模成一个嵌套对象树,但是我们想要获得指令列表的高速度。我们同时要保证,自表达式的结果能够正确的传入外层表达式 。但由于我们的数据是被展平的,我们得通过指令的顺序去控制。我们会采用与你的CPU相同的方式——一个堆栈。
248
+ 解释器模式将这一过程显式建模成一棵嵌套对象树,但我们想要获得像指令列表一样的高速度。同时要保证自表达式的结果能够正确的传入外层表达式 。但由于我们的数据是被展平的,我们得通过指令的顺序去控制。我们会采用与你的CPU相同的方式——一个堆栈。
249
249
250
- 这个架构毫无疑问应该叫做栈机 。例如Forth、PostScript和Factor这类编程语言将这个模型直接暴露给了用户。
250
+ > 理所当然,这个架构就称为栈机 。例如Forth、PostScript和Factor这类编程语言将这个模型直接暴露给了用户。
251
251
252
252
class VM {
253
253
public:
@@ -257,7 +257,7 @@ private:
257
257
int stack_[MAX_STACK];
258
258
};
259
259
260
- 这个虚拟机包含了一个内部数值栈 。在我们的例子中,与指令相关的唯一数据类型是数字,所以我们可以使用一个整数数组 。当一段数据要求指令一个一个执行下去时,实际上依次处理了堆栈元素 。
260
+ 这个虚拟机内部包含了一个值堆栈 。在我们的例子中,与指令相关的唯一数据类型是数字,所以我们可以使用一个int型数组 。当一段数据要求指令一个一个执行下去时,实际上就是在遍历堆栈 。
261
261
262
262
字面意思,数值可以被压入或者弹出这个堆栈。因此,让我们添加些方法来实现这个功能:
263
263
@@ -296,36 +296,35 @@ private:
296
296
break;
297
297
}
298
298
299
- 为了向堆栈中添加一些数值,我们需要一个新的指令:字面值。它表示一个字面上的整数数值。但是它又从哪里获得这个值呢?这里究竟该如何避免无线循环呢?
300
-
301
-
302
- 这个小技巧就是利用指令流是一个字节序列--我们可以将数字直接塞进字节数组。我们用如下方式定义一个字面数字的指令类型:
299
+ 为了向堆栈中添加一些数值,我们需要一个新的指令:字面值。它表示一个字面上的整数数值。但是它又从哪里获得这个值呢?这里究竟该如何避免无限循环呢?
300
+ ![ ] ( http://www.gameprogrammingpatterns.com/images/bytecode-code.png )
301
+ 这个小技巧就是利用指令流是字节序列的特性--我们可以将数字直接塞进字节数组。我们用如下方式定义一个字面数字的指令类型:
303
302
304
303
case INST_LITERAL: {
305
304
// Read the next byte from the bytecode.
306
305
int value = bytecode[++i];
307
306
push(value);
308
307
break;
309
308
}
310
-
311
- 这里,我为了避免关注代码的实现细节,仅读取了 一个单字节整数,但是在真实实现中,你肯定想要实现一个能支持你所需要的完成范围的字面整数 。
309
+ ![ ] ( http://www.gameprogrammingpatterns.com/images/bytecode-literal.png )
310
+ > 这里,为了避开处理多字节整型的情况,我仅读取单字节整数,但是在实际实现中,你肯定想要支持所有你所需范围的整数参数 。
312
311
313
312
它读取了字节码流中的下一个字节,将它当作一个数字写入堆栈。
314
313
315
- 为了能够对堆栈的工作方式有个直观感受,我们把一系列的指令放在一起,看看他们如何被解释器执行。我们从一个空栈开始 ,解释器指向第一个指令:
314
+ 为了能够对堆栈的工作方式有个直观感受,我们把几条指令串起来,看看它们如何被解释器执行。从一个空栈开始 ,解释器指向第一个指令:
316
315
317
316
首先,它执行第一个 INST_LITERAL。他会读取从bytecode(0)开始的下一个字节,并将它压入堆栈。
318
-
317
+ ![ ] ( http://www.gameprogrammingpatterns.com/images/bytecode-stack-1.png )
319
318
然后,它执行第二个 INST_LITERAL。它读取数字10,并将其压入堆栈。
320
-
321
- 最后,它执行 INST_SET_HEALTH。它会出栈10并将其存储为数量,然后出栈0将其存储为巫师 。之后,使用这两个参数调用setHealth()。
322
-
319
+ ![ ] ( http://www.gameprogrammingpatterns.com/images/bytecode-stack-2.png )
320
+ 最后,它执行 INST_SET_HEALTH。它会出栈10并将其存储到变量 ` amount ` 中,然后出栈0将其存储到 ` wizard ` 中 。之后,使用这两个参数调用setHealth()。
321
+ ![ ] ( http://www.gameprogrammingpatterns.com/images/bytecode-stack-3.png )
323
322
嗒哒!我们完成了一个将玩家巫师的生命值设定为10点的法术。现在,我们就拥有了足够的灵活性,来把任何巫师的状态设定到任何想要的值。我们也可以播放不同的音效以及发粒子。
324
323
325
- 但是,这感觉更像是数据结构。我们没法做到,例如,将巫师的生命提高其法力值的一半 。我们的设计师想要制定法术的计算规则,而不仅仅是数值。
324
+ 但是,这感觉更像是数据结构。我们没法做到诸如将巫师的生命提高其法力值的一半 。我们的设计师想要制定法术的计算规则,而不仅仅是数值。
326
325
327
326
328
- ###行为 = 组合
327
+ ###组合就能得到行为
329
328
330
329
如果将我们的虚拟机看做是一种编程语言,它所支持的仅仅是些内置函数,以及它们的常量参数。为了让字节码感觉更像是行为,我们得加上组合。
331
330
@@ -346,7 +345,7 @@ private:
346
345
347
346
这使得我们能够编写任意拷贝状态值的法术。我们能够创造一个将巫师的敏捷设定为其智力甚至是复制对手生命值的古怪巫术。
348
347
349
- 好了一点,但是仍然很有限。接下来,我们需要算术。是时候让我们牙牙学语阶段的虚拟机学会做1 +1了。我们得添加些新的指令。到现在为止,你应该已经发现它的规律并能够猜到它会是怎样的了。下面是加法:
348
+ 好了一点,但是仍然很有限。接下来,我们需要算术。是时候让我们牙牙学语的虚拟机学1 +1了。我们得添加些新的指令。到现在为止,你应该已经发现它的规律并能够猜到它会是怎样的了。下面是加法:
350
349
351
350
case INST_ADD: {
352
351
int b = pop();
@@ -363,7 +362,6 @@ private:
363
362
364
363
你可能会认为我们需要指令来控制这个表达式里面由于括号造成的显式分组。但堆栈已经隐式支持它了。下面是手工求值的方法:
365
364
366
-
367
365
获取并保存法师当前的生命值。获取并保存法师的当前敏捷度。对智慧做同样的操作。获取后两个值,将他们相加并保留结果。除以2后保留结果。取回巫师的生命并加到结果里面去。获取结果,并赋值到巫师的生命属性。
368
366
369
367
你看到那些”保留“和”取回“了吗?每个“保留”对应于一个push,每个“取回”对应于一个pop。这意味着我们可以轻易将其转换为字节码。例如,第一行获取巫师的当前生命值:
0 commit comments