From 8e5624211fd3d5896363f78412e7cc91e730edbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=98=89=E5=BA=86?= <12307130336@fudan.edu.cn> Date: Sat, 28 Nov 2015 16:44:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=80=BB=E7=BB=93=E4=B9=8B=E5=89=8D=E7=9A=84?= =?UTF-8?q?=E6=89=80=E6=9C=89=E5=B7=A5=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + README.md | 33 + asset/arrowheads-processed.psd | Bin 0 -> 232072 bytes asset/arrowheads-scan.psd | Bin 0 -> 926295 bytes asset/object-pool-heap-fragment.psd | Bin 0 -> 805816 bytes asset/process.txt | 8 + asset/style.scss | 607 +++++++ asset/subclass-sandbox-coupling.psd | Bin 0 -> 307634 bytes asset/template.html | 45 + asset/template.xml | 205 +++ book/acknowledgements.markdown | 77 + ...rchitecture-performance-and-games.markdown | 644 ++++++++ book/behavioral-patterns.markdown | 33 + book/bytecode.markdown | 1465 +++++++++++++++++ book/command.markdown | 588 +++++++ book/component.markdown | 939 +++++++++++ book/data-locality.markdown | 1260 ++++++++++++++ book/decoupling-patterns.markdown | 31 + book/design-patterns-revisited.markdown | 39 + book/dirty-flag.markdown | 847 ++++++++++ book/double-buffer.markdown | 759 +++++++++ book/event-queue.markdown | 1155 +++++++++++++ book/flyweight.markdown | 451 +++++ book/game-loop.markdown | 934 +++++++++++ book/introduction.markdown | 300 ++++ book/object-pool.markdown | 680 ++++++++ book/observer.markdown | 945 +++++++++++ book/optimization-patterns.markdown | 35 + book/prototype.markdown | 786 +++++++++ book/sequencing-patterns.markdown | 31 + book/service-locator.markdown | 807 +++++++++ book/singleton.markdown | 737 +++++++++ book/spatial-partition.markdown | 796 +++++++++ book/state.markdown | 845 ++++++++++ book/subclass-sandbox.markdown | 667 ++++++++ book/type-object.markdown | 934 +++++++++++ book/update-method.markdown | 797 +++++++++ build | 2 + code/cpp/bytecode.h | 363 ++++ code/cpp/command.h | 287 ++++ code/cpp/common.h | 24 + code/cpp/component.h | 630 +++++++ code/cpp/cpp.xcodeproj/project.pbxproj | 269 +++ code/cpp/data-locality.h | 341 ++++ code/cpp/dirty-flag.h | 183 ++ code/cpp/double-buffer.h | 378 +++++ code/cpp/event-queue.h | 378 +++++ code/cpp/expect.h | 33 + code/cpp/flyweight.h | 264 +++ code/cpp/game-loop.h | 144 ++ code/cpp/hello-world.h | 15 + code/cpp/introduction.h | 20 + code/cpp/main.cpp | 31 + code/cpp/object-pool.h | 333 ++++ code/cpp/observer.h | 739 +++++++++ code/cpp/prototype.h | 190 +++ code/cpp/service-locator.h | 247 +++ code/cpp/singleton.h | 353 ++++ code/cpp/spatial-partition.h | 577 +++++++ code/cpp/state.h | 743 +++++++++ code/cpp/subclass-sandbox.h | 330 ++++ code/cpp/type-object.h | 304 ++++ code/cpp/update-method.h | 325 ++++ .../pigeonhole_comparisons.dart | 76 + .../tiles/tiles.xcodeproj/project.pbxproj | 225 +++ code/flyweight/tiles/tiles/main.cpp | 279 ++++ code/flyweight/tiles/tiles/tiles.1 | 79 + .../activate.xcodeproj/project.pbxproj | 222 +++ code/structure-of-arrays/activate/main.cpp | 136 ++ .../combined.xcodeproj/project.pbxproj | 222 +++ code/structure-of-arrays/combined/main.cpp | 165 ++ .../project.pbxproj | 222 +++ .../component_update/main.cpp | 299 ++++ .../is_active.xcodeproj/project.pbxproj | 222 +++ code/structure-of-arrays/is_active/main.cpp | 334 ++++ .../random_array_access/main.cpp | 81 + .../project.pbxproj | 223 +++ code/structure-of-arrays/shared/utils.h | 81 + draft/first draft/game-loop.markdown | 517 ++++++ draft/first draft/introduction.markdown | 310 ++++ draft/first draft/object-pool.markdown | 478 ++++++ draft/first draft/service-locator.markdown | 602 +++++++ draft/first draft/singleton.markdown | 438 +++++ draft/first draft/spatial-partition.markdown | 388 +++++ draft/first draft/update-method.markdown | 258 +++ draft/outline/context-parameter.markdown | 47 + draft/outline/double-buffer.markdown | 297 ++++ draft/outline/game-loop.markdown | 124 ++ draft/outline/hello-world.markdown | 76 + draft/outline/introduction.markdown | 113 ++ draft/outline/service.markdown | 95 ++ draft/outline/spatial-partition.markdown | 110 ++ draft/outline/subclass-sandbox.markdown | 204 +++ draft/outline/type-object.markdown | 427 +++++ draft/outline/update-method.markdown | 316 ++++ note/bio.txt | 3 + note/description.txt | 3 + note/dimensions.txt | 52 + note/fonts.txt | 88 + note/log.txt | 947 +++++++++++ note/print workflow.txt | 21 + note/publishing.txt | 84 + note/references.txt | 9 + note/style.markdown | 125 ++ note/todo.txt | 4 + script/format.py | 501 ++++++ watch | 2 + 107 files changed, 35496 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 asset/arrowheads-processed.psd create mode 100644 asset/arrowheads-scan.psd create mode 100644 asset/object-pool-heap-fragment.psd create mode 100644 asset/process.txt create mode 100644 asset/style.scss create mode 100644 asset/subclass-sandbox-coupling.psd create mode 100644 asset/template.html create mode 100644 asset/template.xml create mode 100644 book/acknowledgements.markdown create mode 100644 book/architecture-performance-and-games.markdown create mode 100644 book/behavioral-patterns.markdown create mode 100644 book/bytecode.markdown create mode 100644 book/command.markdown create mode 100644 book/component.markdown create mode 100644 book/data-locality.markdown create mode 100644 book/decoupling-patterns.markdown create mode 100644 book/design-patterns-revisited.markdown create mode 100644 book/dirty-flag.markdown create mode 100644 book/double-buffer.markdown create mode 100644 book/event-queue.markdown create mode 100644 book/flyweight.markdown create mode 100644 book/game-loop.markdown create mode 100644 book/introduction.markdown create mode 100644 book/object-pool.markdown create mode 100644 book/observer.markdown create mode 100644 book/optimization-patterns.markdown create mode 100644 book/prototype.markdown create mode 100644 book/sequencing-patterns.markdown create mode 100644 book/service-locator.markdown create mode 100644 book/singleton.markdown create mode 100644 book/spatial-partition.markdown create mode 100644 book/state.markdown create mode 100644 book/subclass-sandbox.markdown create mode 100644 book/type-object.markdown create mode 100644 book/update-method.markdown create mode 100644 build create mode 100644 code/cpp/bytecode.h create mode 100644 code/cpp/command.h create mode 100644 code/cpp/common.h create mode 100644 code/cpp/component.h create mode 100644 code/cpp/cpp.xcodeproj/project.pbxproj create mode 100644 code/cpp/data-locality.h create mode 100644 code/cpp/dirty-flag.h create mode 100644 code/cpp/double-buffer.h create mode 100644 code/cpp/event-queue.h create mode 100644 code/cpp/expect.h create mode 100644 code/cpp/flyweight.h create mode 100644 code/cpp/game-loop.h create mode 100644 code/cpp/hello-world.h create mode 100644 code/cpp/introduction.h create mode 100644 code/cpp/main.cpp create mode 100644 code/cpp/object-pool.h create mode 100644 code/cpp/observer.h create mode 100644 code/cpp/prototype.h create mode 100644 code/cpp/service-locator.h create mode 100644 code/cpp/singleton.h create mode 100644 code/cpp/spatial-partition.h create mode 100644 code/cpp/state.h create mode 100644 code/cpp/subclass-sandbox.h create mode 100644 code/cpp/type-object.h create mode 100644 code/cpp/update-method.h create mode 100644 code/dart/spatial-partition/pigeonhole_comparisons.dart create mode 100644 code/flyweight/tiles/tiles.xcodeproj/project.pbxproj create mode 100644 code/flyweight/tiles/tiles/main.cpp create mode 100644 code/flyweight/tiles/tiles/tiles.1 create mode 100644 code/structure-of-arrays/activate/activate.xcodeproj/project.pbxproj create mode 100644 code/structure-of-arrays/activate/main.cpp create mode 100644 code/structure-of-arrays/combined/combined.xcodeproj/project.pbxproj create mode 100644 code/structure-of-arrays/combined/main.cpp create mode 100644 code/structure-of-arrays/component_update/component_update.xcodeproj/project.pbxproj create mode 100644 code/structure-of-arrays/component_update/main.cpp create mode 100644 code/structure-of-arrays/is_active/is_active.xcodeproj/project.pbxproj create mode 100644 code/structure-of-arrays/is_active/main.cpp create mode 100644 code/structure-of-arrays/random_array_access/main.cpp create mode 100644 code/structure-of-arrays/random_array_access/random_array_access.xcodeproj/project.pbxproj create mode 100644 code/structure-of-arrays/shared/utils.h create mode 100644 draft/first draft/game-loop.markdown create mode 100644 draft/first draft/introduction.markdown create mode 100644 draft/first draft/object-pool.markdown create mode 100644 draft/first draft/service-locator.markdown create mode 100644 draft/first draft/singleton.markdown create mode 100644 draft/first draft/spatial-partition.markdown create mode 100644 draft/first draft/update-method.markdown create mode 100644 draft/outline/context-parameter.markdown create mode 100644 draft/outline/double-buffer.markdown create mode 100644 draft/outline/game-loop.markdown create mode 100644 draft/outline/hello-world.markdown create mode 100644 draft/outline/introduction.markdown create mode 100644 draft/outline/service.markdown create mode 100644 draft/outline/spatial-partition.markdown create mode 100644 draft/outline/subclass-sandbox.markdown create mode 100644 draft/outline/type-object.markdown create mode 100644 draft/outline/update-method.markdown create mode 100644 note/bio.txt create mode 100644 note/description.txt create mode 100644 note/dimensions.txt create mode 100644 note/fonts.txt create mode 100644 note/log.txt create mode 100644 note/print workflow.txt create mode 100644 note/publishing.txt create mode 100644 note/references.txt create mode 100644 note/style.markdown create mode 100644 note/todo.txt create mode 100644 script/format.py create mode 100644 watch diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..928de15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +code/cpp/build/ +code/flyweight/tiles/Build/ +.sass-cache/ +xcuserdata/ +*.xcuserstate +project.xcworkspace/ + +xml/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c2f02d --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# 译注 +这是《游戏编程模式》的中文翻译版本现在仍在第一轮作业中,请切换分支到cn查看。 + +第一轮作业:将整书翻译一遍,翻译质量略高于谷歌机翻。(在这步完成之前,基本不能看)。 +第二轮作业:修复漏洞,并平滑语言。 +第三轮作业:校订。 + +如果有任何问题,请pull issue。 + +# 原先的readme如下: + +This is the source repo for the in-progress book [Game Programming Patterns][]. +Issues and pull requests are more than welcome! + +## Building the Book + +The book is written in Markdown (in `book/`). A little Python script (`script/format.py`) converts that along with a SASS file (`asset/style.scss`) and HTML template (`asset/template.html`) to the final HTML (in `html/`). To run the format script locally, you'll need to have Python 2.7-ish, and install Python Markdown, Pygments, and SmartyPants: + + $ pip install markdown + $ pip install pygments + $ pip install smartypants + +You may need `sudo` for those. Once that's done, you can run: + + $ python script/format.py + +Make sure to run this from the root directory of the repo. That will regenerate all of the chapter and section intro HTML files. If you're editing stuff, the script can also be run in watch mode: + + $ python script/format.py --watch + +That will monitor the file system for changes to the markdown files, SASS file, or HTML template, and reprocess them as needed. + +[game programming patterns]: http://gameprogrammingpatterns.com/ diff --git a/asset/arrowheads-processed.psd b/asset/arrowheads-processed.psd new file mode 100644 index 0000000000000000000000000000000000000000..aaecfd3df5513ee3cf5882e4d76e61fefe8f9a03 GIT binary patch literal 232072 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

e?k4i zV4htaexY|D4#-ZL1eYQZV}iFoxD^c6dQA>&zaUFNQ-Ya$>dOSCH#l9vQi3UJAp6H) zH5~BuHEWmi=zMj)4 z?VH1`&zdAnwqe?_+$Y;c6jf!K^%6{+Q$8pNi<0S*OIqwx(f4X& zc({_scF7G!xZJAhcJYRJ5LA<`40F_ml1HkrNhdwLjh7^sKZYMLNkfH_R8dypkuZzZ z+MY0_pyxZlLBHWiME=jV(AxoEh8c|v109lfWU)w|_lmJa4 zo?7MkQkyJ=y3oGjGRY-@p9TVStQ+OcYf0H!nUIfE$>Od+dwmi7q+TsmQsBj7?|pGh zs@(h2arebL!+zg=&Q-uc{XaCf8PU>~Zxc zCkcP27(I9%JO!klgkXZogDMX%DjR&nVizX!s#*+2ud@-$URAAd8R#_C#6!YVGSp7Y ztq2*Rs+Pm2UAFU(Ob-nPZ1lqu0+{=-bode>Ut)%a!RK@pU78wNiK^vF_2-Mk^7F+# zQ`aeKh;;E>VOb@JGf9HH$sbi|F+@R1DktO%_#?c>z{EKai7-{>uRKRgkw*(9)pY(6 z@82A2S*2Qeas(<`E2)*TQIgmY7=Vb9@6VF(Ij6I~`3<~qpmrjE=Tr9{m6Lcl>3d3u z>3i96s;UJb;!*|j{&tdB&w(@t@85RQrIVi*6H~g2uPI@1xGOidJwd`g&buVb6&D?T z7420m#j<*`RS_`_a?3_3u9A{?G>3$I>{2~>#|nLE%Z5Qfi|Nc#A}b3BWV@<)Gt^M% zCFoKiAGS(ao~jTMd7N-@PyzvCCLX}Wf>LW zV^$ABKo>@Mh`H@6EBo+-8$HDhNvI%Ub1ulXQ-uy$+2Mz`0pek3gW!ZjRsQ3VJNP_5cx|Lu3u!bZ2 zNd<`A12rYVuoZ2oIZ5+?Mbp)F!)JyvM<-9*yp>_ob9$ghSl$_BaO+_6?*UX#%RF}14TQOPk$aw=XPzonWh zRduI|IvQMne8}f`iLb!;ak5O7LOj{o7u?5(7LxE*Z>{}))5&5qMU}IBDme*tq9*q3 zGGciTtb55p$R$-GRp@7{5uiaTbgPF{b+tm=BqY^^*r4R8fZe9%xA@qvQz4t}ck&SW zuuHHMAen|cy&B^ko{y_mppZ|$1(wD*Na)a`ps*~jj}t=x=ag*Fn+lz%*(&F_1S{r2 zk9;i?kyyVhlsz`dCUb67XXP&yOZm{OdN92eCr?u$ft5fs(p~9R&ib?~U$-S;RIgN5 z{`b)`;l7;XB3#TyG|r88`$|H(;#>CwjLJlwOet40v0FG_^_yuct!QJQ%V0 zgGq&i`{cd>5*Y1;U53+9%PlPM4xi-L_ok-)zqFr zMX;+F9YUroK17FgRWf-li~zNRR+dVjRx+q15LNE%Vv(Rb`>3+%Jd{Hwm6TL7=)u@) z@wnW!gD{;NFQUSF^0|B%%cFFxb3g*E=g*a4yhFF~rRT~#7%zC$iXd;rxe6IE)Bnfb zn+C~Moq3-pGi#G%z!0A9iNO60N6gz{mIBWt9c9@2?9S{&Ocrdmc zxNWyQU=YHR5sLs}KtgDj&|(P*EofIsNGg@2qEw|)tyz^z<$CYUbJxt8-*dCFDod`U zUphSVPDdzEq0(K>dCqg5^Q`CmZ*IsWgB`H!5B#@3G9M8y2Vu@Mse@c%ULzxi3aV}1 zp{}CuWk!TtjdP@8uk`rB+(~l(1b0{>dcFg>u^rk$5Sx7Mq4A;jdaqyVp;)R8)Wr`jm;E{37|ilg2FX4$X;_i4wtCy4NAAi0V?YdT}^}1rD;=fet z!^&8PQ^{9)j&mcEg1gAbj?XFegFwYz7#e?QL^ba~guGz=6n$4%e-9N^^XHD_M2-5< z(#S(5b}VYd3)3uJQilqbHHFQZ^3ElsdglgMg zsXVLHefw96Nm1&ypW{*8)WamEzHX|1rOv-IMZ{>ONo1(VSlf}lhtB0Z7av3_5>Y3= z%~a~#^uncHHNK5kU#-k#qP)}?OVZzOspfmSSe`*gC7Rmvos^1>d7tf8Sd*TL&P@dm6-P^eZi;yyN6Dr7BZl{rBs zQL5xRjLd!-y6vKHIA19mR9b>*5>l1c+!(FocRYpLyOE}dbFlHXS*?=M4jI((NP!j^ zXW67qcb!WxCNWVZNQ{n)gSe%lBA-1k@z<+}Duapo#c}NG&k&e=Q%+-j&x&rNz~uOP z<1r!V4DR4ef<`Nm#Fq>;_r7u8Ddw5MAU~6qlVvFvLWd9|Zk&`5n>;@3;Z}l}@3K%? zj?tbje9mb9ncowP%on0wsWgit?O|%vAw(wAibktA#iBloLaJ0+oRs6|4lO+aU6Jp5 z_6#;9i198yQdBK(UK|@QYF=b5O-e?UMLmI#Wr~Z*@}NNHYO|P+r_p^e&w?HPjitn( z?@I)k`%tOpbmjPMu2R33nOu&XmO6e=Qqdncyny!g@DjoO2vTtj~mWeXcl@ zu2dzO*zy1#)(%!6r6VK&+hf#knP;++>gm!&@DV`Bk#I~}-O|gQ+DB6%PA|E;WGe^z+ z`a37Px|c0`ef^u?PpC*Y6h{2~ZkYCG!XTqmf!UUdJat@5qVJ!m%nLL!$*(EHzl_1Y z>EGvdsGR!LcOIBO|Gqu1{Opkz_MSsF>er!LnM>C}g~bq`*>}Z zkFJS5mGH3>E#LYG*H~PsiSlOxx8xPiv4p%h>n;Dm3qSvkQk|-01TyeJCn+V@P{~y; zLTNY3Gm7tVk~zn=k@WD@9z2>?9uTGTkt(QpU+VDm$WX?&;tAyDYFZU6nk(z;GOM*T z@)`=~Ss(Q)Gu-O=u-nP~oFP1GJX=i&v)35b)@4-U*E0wzE;?T$tcVqhx zb)--bzr+*%ty98>n@O>Qb!K9jxLCheS!y%NvTFP6);JSRj4lIY8IL1d-57&M;{4X! zzvrF*bzUr^&Lb+kM`Y&-m2ZSLI!Y^$`V(>#ERAE8wt!IE_v5?fCsl0i`iVTxLrq!tl~Im@+GYJX@v=;dt{=I0Q(3kAVm2v+Sy|`mWML)AYS2>5IcB91|U+Iuk1EP1dGv$%>7#wu4gvKzFA*%k8iaqE1 znTm(j^sJ1?rMT>sNakk=au-AXMHzD0zoBO&uN?@=d8E0F{-LrFvq~k4z^`;YzMJPK zo>TV?`r`z19!-h5xSGg04vB-xaU7kWuoTv#DI%mXLJ4ScP!k*5RT{P=m3mM7$|@I| zm@|C_GW&f!9?GXxY9|^lvS`On-e#zla|C*qhqn5vmEa@ic$OFV%~l>RipoMJahJSq zGJLI$)-vB$5$0A}a2EVhGK1+~#dRG1aG+iQ*Ie_Vw@V>iC&@9cH%3KT9H% zH=thfqF>P3zJuDY5F8Te`r`~jJd(*woa{`Q$xf)2g;lA13mf?KncHjOkkw`zxpRANRjE}n!Tkn$sg@{sVyyQDLMeOtNOBT$<(fta_v zWJ>cP&o_!tGR;i7jN|ST<%V&bUm8NdVj9k$lzhZ@UTz<`=&5y+%GuVlR+A((&Vq~&`EV}0*9n24-J z;QixO2g0LUu^usWEE=ihg%MM`Fz{+>O7WF&|K+{_Qm|j- zJbY{DlA7~uJlFqW7Yi1B*;`i0!bglc8GJb$-Xi`p`FUTf1F{Nl2;^9d{DO1%53EVY5aVHK@~qSY0m`4eo-JO@w7Z^>1NUMu)mF-bTQ_vFHuA*%Dw zln4P&z?J&6A1>#4ve>_7VknUWgbXDtR4Jo3GH>l<`>%WCq~o92vh3gg_h|HoJyZz9 z>`ZozL*=g}ulRjJlOSaj2Vq1Ea_N?3%7hatlatR|Dz@DZP@B_pLBF}Yiav!)Lsp?g zzbqxIX!ytr(tG@o_r?s@Z%Xdp_+CLYy1e-|`>+DswUM8XspgIJbvz!3_Zy5==6Pt) zJWC_vow>(S(UG8hyso!U5qQrP)Z8yIOpzj3ihu3+s73XZrLe_AT~K?29_tXN)ypMD z$?Ew`SgmiIf0ncwA5*RBh02)vPANyW3uz#a$-4CWVDImDoz2e3)4G)=)ItBt1vTfr zAs9mi%7MvI6fcN$R}S zD;1f4an%WQr$;PZx1gOz;%*UiHIul~e)(t}%dLuf zJx4l#T!Stp(X36DpRvxE3hUrGMz%r$1pxk!z179bT@Kydkk; z7~OWfL-G*OzT%ATs4WGRoLAYARZYvRisL24Ru-YkA@)sK5~j%zYe8I9o+g5M1TB7? zfC4>G!kFa!#M0Zebo#mVmP4mf`09I|9QEsdo^@>7^@Ak1kR(^%+FfI%%sZHi7rN1j zh&*;F7KD{URgzdT46oiXRn)IILLB6;lt~YTl$24OG9ggH+d|14Rw=5U zY-`F_mTFCq2o=kyD~?$Iv0p*J#_=$6y_$dtCafi>BnCbIopBXe%c5;1^b_uMOZT)I zDSb&Cbo$R%O6jSTwcbhi#2IplL5s&PIm1R@x<@BkvDV_^bO8q3vdrfzAx za!*AOsqDST!i#4IBV`ct7&q&pir3pOe}O zKHmJLpH*)nUt~gBLYKz)9E4Dv^dMxiCa_MlZH*Giu0(Z_4x%qBRaC9NLXa_u(4o-C zk@2-8`Jct1mkYClZ+&(jB6YA>3knWJxK0wW|xeiTrO1Rl!UKz z*h3*J@*bgc^Ep`)WZ%(U5~`pZ)VBI^qg(mRI5CKD!5^g05h&aE+}oc^tH|3rlz$lE zJwUMW3I_X9NT+xaB)>E@_7((((#DR+`Og}vd2I!Em(H4fZ!F{EQ?>rUsiX+PoP9F} z=nxa6w6is>KK=7?@nH-Rk`i9N^Csi_)!uxqk{|JNqJ=H!uRKZW-BC9L0D zDP|-fs`=!_ii?`G80E&KihKo&9)kI zXhzWGQ|%-K=$F7qe|H#tVp@53#BrZ4Y!wPu@fkDTMT z;pskigbXgBdTgu*|3Il<;zUYczCV*}%WulGuN{0Lg)>s5>h5RIh7U1Qd#eJukV}%E z>M1Bi97-IOduRT9I=}&h?D}$dv7(7h8M8MpsoIvEM~qQELgOBX$h}5mR9rb<9Y_2A z5-%*~R7S{2idDRp}(2Y3QMh>;#l-gA+K7_ls^nB(V5pe7)jHovE5|7cfU;2Pn zAAn3n;x@JkIo>g&Nr`1~+x*sk&m+e1U+YoL&sM0^6>GztJMI^;SG1LQgyO|TubBEd zoqcYN6>O_(>JpCMZ+lhKQPjmyRn%oSN@V;Is)p~7tf=m#sZL6d81w$0U1r+^k0)x6 zyfO2}Wn+_CGvLDlpo`(`Fr)WQn3Sc>GO<6|oFnz5;lsvb!f zd4$!tiI&wq!lnl-qgCtBtf=3kp+1WZm-0_}OWmuNnAse$yC}51W7I=mBaW}=+N(A# z!`Ao65@8+7o9o`}LAMfRI2nH&rO=fKbNF(;Woi>BW}H$OF65bc>N{V_sz{qvzqN=4 zFlcSmqQg17j3y(~LZmgdPOvN5C|8pJa`w4f8;!q+&HQmx%2 z>|ytm+&R?cC^Dh){YY_rK}9#bp`~O}TeO`bzrRG89TzXoxm4MP?m8bnP$X{5SpGI- zD*EieRc~Q2z44J3aw^g_&Z5xX%r{9ui>7Ld+<2-~zPdkZmiSsd%P2>-ZcB+q*8a@b zo6$2UF;v}?m&;NstS2{yE5`~^DWtv~y~w5oUYBPUlG|;`kZJP#dpyi9l*m z)30m4C6#dWNZ0RCAG=^U+q$M-#v%3Ee@k|xh0H#LmDkL8nS()o6ip`auRs0@P9yHh zL{M9g#mbE&51^bh#n{Gqx1rQ@@b-)rIAcV>+3YYrgFOIEj}(c!NL)=xjSOJC$bK`f zN4%3O>)t*?@_2%RMP^LWLAc9f@4SrdZT;o^*GOQMP<(1?+S#t=&l&Y4U4(cBtLmCL zMKfM-15(HKvO$uaTiJF*;wq?aLRd&nFjn?(5*^o#Ikr1v1ns;W1Z;@oWiQb^|D6O| zOX^>igj1QqmTaJ{@AV)8$O0|pjG(%eqdH%;iD9Q1yk)-)b9pu-cIN#9c$V-frt=2% zh1xy&yQ*yV(>$fJ$?Xue`_+2~pqY>gFMXo!(8ct-wWq6pSKqmIZ6*h5e4csr8n3SL zMI_>y99-kUH94s2#%pWg+FH1_7Ot&@DhIBug==f!+FH1_7HS;0rVp;^gKPSrwi^B` z`^s`YY&bYmd5XePx%|{*PO3JdZgNe-q`=Np{y_2UukY75(0Dbh49~rm$7UqEs*kTE zT^(@R6}TdolRC1k+2wpmnSI|*dGA#(bX87qbsQ?kgJyQq6vjE$Qw%I}h-M($JY;)P z^<3F7L|=mF)0IW+*AB{mm>Sd}vYh#HiH@2yMZ}Dzg^Ro?32*Xp6j4GPpHUk4h(l$k zE6>treFFw}$S-r61u{A06P{RUqCy>zMW<%NDGoL=0If7Pyhh4Wwz>u!59Bbf5Gdt6 zt61c;3;P-520L#Rzgm79hf7l=IUQ}2;$~RN-EI+7*-{%E#=QmD6=lUn$G6 zWGX{tCzybMD7%_nPC7WGR<-N|Rvb2448J62$dLd=4fg3NPLO0}M}v}G_mB}*Xemqk z0s8{j5+zsI?87}$^1&QBB&#Wdgh3oe_81@_Qf!DKlg!@}?5bev0>wUPm7b{sw^>qE z<_|ghDBCJM$|!8^lZ|U!;W{r=ZsaFyf#ND92hy>;i9AB49FwjDpYg5nlpLHTjSLYM z`x`k8=P*q2wHyWsB5pWJx6HrF<%kvfDjqW+PU}2=-<`13xjUP`uhEZ-g^k3a5bDgvWeN2(NhtT&g4^ zDOdiHL^bn7KDmIb0*1tRm_cu>3V9yw?Oq=kYfsuihf;=0J5ERA)f&3~{=LRid=TvlL{2m!@ zew*X4@sFV}1H9q4hWlMPo9ME(*nK2}4A}u2IS2+|B~%zmfQ8=tJ_^Niq1=uFKeNY#6s;2k!ZhHfMK zs|n1+b(M4VeuiJfU&qBIj4Jy}CmWoe<&TJ6TsF~ij=*N`x6AP%2@yPh0(V<>Yfpqh zS?J0_l}9tulsKjwoZ&v3R0Eq1Jb%SsHQpVt)e)#zn}hA8vW_|usLFO(CmMDbgA+7Y z3I0ynm)3E#`9F>?VL%ux5dN{-CkR@rzA$d~WbXtEM0e%9+002bU(@_JF|6b!?~;hu)X> ziAUfFn{8bJQ28}6`F6ej>*i^oHNv-2{LN(hmuGoyvqZ%-**nX<3$$&}4v{ONMHJRG zlGFyWCPLi!7JGqb6=IR^zv->5NS_4i&aB;St1`3E?XBD<;DkxDt;J2ScJ}MDE0@g; z705Q2T#eOcW3mt_`?8p-*}H2@Aa~r^-q|Qo^sMvV_|7IFq2Wu2=4@iL5!z=bVD{^c zdCI=US;SO`a%U$hyfr&j;ZPdgYHUjy-T2nM%tZC z;AVTj5p5i{LbJ`@&L^MD#*!>rdNv+x6m9>vxzUK7jjlIhNu%qH+3~j#pT_OauBSG$ zHZkzCF)vhAvugZLD$|=nr8iq2;hkBh?mxIQt43@5&bvrw=o8FFL__iUq|6i*E-un+ zoiy@)8~c9^{lZ3`-N*QaRYN>0qvaO%e|DN1jis>`Z1jfYU1#s3@tsEMUVNF^+iFZ# zqdScj>TLS&+O=s8)ccy#SEoJxYuBbZ@VDUAR^pUtJ1uQjdA&`-I4X1lQ;t#nJ&TWy z)b7}*dWmwL51*{PUH^m=MosR72lSx7uI{m2yb3A{jpa~rrZ>g{TpivA-VxvxInWoy z(ce(_&U-u=)V*mvW4kSpC=%cWMg@GNkO8vr30j%pb=eMV_iEPE4u<8jo!1JG`jv>uLMuPHCD&^FY z9d0luOG(rP!!z5*0GF#}u34ZP$E@)wT?%lg5)A_PzJC;kpI-n%PF+ek!r+X{C0$&byB-==dFH z{(jd{d!It&`@rBX>p@^at*L+WFqKm`F+f@al(#2(g9Lx6qQYRM22CZ=DA2p|_SE&=PBqrTNk0J$?)zTh%H#+jB12hI zodyPX2+|7H%&|u^J#PSm`ztL3C#!i93UEZ7VfPF5^=;&n7H0lLC0)zWa?bY6j zaH2R+=YhdpZU#;T_^;}g?LWcRU3f8MKx51wMNX%XSBW``6|;x2)~{oRJ!Ih1|x z0uG1X1q|-8gqOWM40gle8SP$AJ`9;D6_F_8)b@LU!F}4z+f#I!fljl=;~>Gde~(kH zcOTABi&c?@OTd}f;F2#pHlsc2?o85zJE0>zKLQ5#RX1bQ8f!|khhl)co>~Qd)i)Cr zPd!gF-vkEti4~^}GHJFTLA1|kajr{AIta|HG5kBw&{n(FRD%jct(bO44@X}5CNQ`? zoYBl0&wuUVuW|PDru%`xU4iOxosw+=E%f1xR_Bm@(v~@dVgLsBfa^{U`I(@_DzLyp z7~HI9nwC=lz93@s$x&@!a5tO2+hi7)iZgW$_5|}>i1duquxp+EG0+Z=Edbc$M{&L! zRUQ5_!9Ir=CDIZ#@DMklU6)Q$VsDIjpXjs(J1SnrXu8dm5JB!9m9Forl77@9n_g zx{;!rXWbM-qW%;_3K_JR!;Gv8VQ^OudrsEPdxF*EHd~odcYu}kDO7L12l!|+i%c@&T6r53P;WGd)7G0%Q5|(ZHn(7) zbUVKW4DMFs(lF?;i!-A8`buD$0(BOW{Coi5abSqSeYgsPd$y02!c?ggJ%h1ZaodKO z-b8=A=WD>=Ziko@LuPE+G}&IF3p9W{N_05Ej+JKFX92cA@M*L>dv8c9;R^AHg9eJo zixCU|KzVDYs2!jq^eWb&Wx-n=V%95e)Jfb0wcp&3bfMO*QOlX|AkzO<2!ne&A8CuD z^gU$=sWIr$tQW`B*|Vt5ohdr}ls7bhvOF)gj&&3?$A{9`JEqM_4yD;KVpu^lhwJl0 zIBYR5Ge9btPq10W{{QwI^AT|}5T>U|9SlK{gUHZ9fx$g^A@4frAtBdtTwrjQce}#e zNpk-LcUU6UT_Ftab_<|mugPVv5X_%&uXoG>KtJ$BydHfaguxXX-u1>JMmfjYupLq4 zeg4rzI~k+QHkQ$YsUY)6MBRNuR}h^2&Cgc6|GQuO@;+d2?<)t_B~Bka01WPt{%kP3 zu8aB5;(}I1VlWg-bryATcTd@!h>d`6;?mo|(aDi8m zWv82+)UfGVGUBdb02*Hc2KTY|6M?rzsdZLRNDTi%sZ&AxtPZk4_c3my6L1$9*$xcu z-N4{38%R9}4DNO$!SU8l(RYRQ_n^Sw9?n3Q?MBU^2Z6zLqlMwzG-XSINcQ0x4DOFn zmjZBQnV}4@SX}pdMg>y~mT4KIR^RQw;O+~8MM~Yf?=3MYO5OUHz~FW>xrwiZFu0xX zOc5~}G=XL1<{9e=r0+p+!d-k2sYpb1b}NXC9n%Y!npA2Vuf7`0Wulx!oF(b*1L5gM z7g(NNM-WZyz60dLxKo9}?GCK;^mtQ2EM2fP6(Bd#w-_bypuphXb-@Irb*jg+^!UVd zkKH61#>e!#boxpV?!`i6-7b)XP9xtK35p2ojyh6c-Z z(3FIF+N;fKm5dXRK^>12XpwQU;nMZ5vkAr|CJMaKk(4-yn*|29Q{t~z5mh)Db@M4; z-5y0?vP~Ha>w8vo8wDoqS{p(b+|#&&GYJ|EBBNgf2KU~StMgfwg-2*po$o3w?PVz! zLWd9|ZX6{qugNJDEW6^xsDh#JSQXtD@HwM>rxy~8+$BW45;Th=?OTH5PTU0yZa8f**6A?-J$i||+)+FvQ_GwsU{uV&IfZR^hcLKr;f2ne zHv+G0IcOvH7$rnB-)F(zFEF@wZ%CuRQU1g+F7aHJieeQ8cRNAoh3_rFGop&v=4)<> z6(mNn3KB+SBS#(u2KQ^L+b?uI|NM$|Z+s6pmM&toZD(r5*(mW$VBxagYLlcR1wFu32mh-)m>*9reKo^1jv&k}NmS#Q|`%O1NUguxw> zjiR10Nh!I8AQPO2(k_%|6yM_{bB=8ThQL+32}zgVKLvyP#j}nzG>~?U;Zfw~YFcHR z@mx_aJ*-x9=v5TXA|LfDGu-O9uv=#yV+e~1i-5s>HScD#u5YuXlf7rQt$O}0rS4z; zUg+%mM*Ed-i2apJDYN(6kMfZB*kwtpG2!Z$B0SxY|ypdfxh81Wlj!iSqlv4eGHkdhqebAlx`7~FSLgUbpMwK7R9p?)OuDloXK4Sc3TNN$S+CvMM! zSdv?TChW(7)SX+`ZCLfvz3n7#jI85|ja~CRshlQRrbu7pyujf8_=yE?w5N0sj{C3Z zB{D1o*3LUeX&MB6=Joh)jyt+o-P`Y`2&SCO(uOF93E~yh#D)+C_f|k;c8OnEe{AeXf-X z<_+o?T?m8wgrqV;VO$wzf3jAPKL`x&*YNM& za?SoSb7t_;iSU#{e(R5c!F^s7pXj%*-LdoRqiO}r1Q!vtm?nO`0rip<{esqZHEO>? za7d)8~3{^46ZwbR+LRJ?!FKP_uhRML?T^JW_+d8 zVO-pe*gDrl4Yd#vq;RcqiqOigX~0|@-P=(fhh4XmH)_r?YZyJNA4Wwj1R`zoh3EUi zo@89^1z>Pd5WwJW?oEr?cS8B1H=Ml1z~HW6Y?*_@jIx05+9`1@^TSsdTyqt$Twgp@ zxvDb>oMBwkur}S!p`g_z4%cpeQ3!+kD2lTA7g*3Z%2RAzZD{W4d+~HSi2x0v9TS7q z$$doGZNiyw+UaTm5~uR@BXfgTJP6hxjkg{_iG{t9p~Xc`gfO`GpACZZ>)s_X62y-c zSh?bIO!b(53)?$y7pychvS6U;AfH(Uo{tnVEM6Ojc-)NRV>t~k?3=YY*Rz~J8f@RQ3|y!!mp z3Rk!J{yWq+m-G`u4=>t|Sw~kiS=qFKLwi$J(omLhcpxy5E>?-*Nlp)P7BhR6*c64k8UhHRGv z7ZzC{*X@P93wNFwpOL3^gC=O~dkGlaT>~(N3Y3P)Q4}+6AD5&oH%%ztY+-ky*GU? zgu#uY4M$K1RdF{6x|&H`5&RvkW4RTm6~N&Bs2gE4li~vyZrV5v46fVk=)!p3DTNlR zZlE`bdf*RElI|NVNRexI2!q=bn9h*cF>+NW@D9mCM0@iyx}&z_1sNyU4uszG23TQ9 zv6XqK(!{%X43OCC- zE|uyBNpK-auHH2}<7Rp_=3-eFIuVh_F2%gCa;Qv+CByJ)H3+nBpvSS1rRnL#u{SYv zfw}#QxMHg97YrNG4}h*z1bh{Do@1s9;`(Rei4DNuuIVKrmvU)6CHS=1FMXjONlYvT z26w@tA+6Kdf+?Y+&Sd2{9aO0$5Rv68ax&FbBG#Zom`WnJa&W6AVQ|0sE>5}eQX%X{_;-J%iHZapuDa*C85xCla6AGSh;RMmnU}p$}n~?&n zf$o>XnuJkViTc$+G{<>v$di9HA+4ep_`sVi?R4)7U~um~MQzohLaReZ4*3)q+`CvI z5W%QLC?SFbO~?eIG?uH=Ox?1u{6`@S?)&FMBP3_{=Ae8XGB2(^jLCbSh|7X#iU3FI zRv}+wL}4@HU5^2S`^_2*?p;ehtKLAq$b__nE=@sTa93l)AY?KvzfQDmjS>S7A!_9@ zfzg*j7~BO2GA0o^R35XNT0@e55e~(Kcc89&M9LV=8c0dvFn<3&^pKOmwGI2!-$PON zQ|zAEC1ddYLNLb^zA~b23R#g|0)yKj31OrW-6f$4xU|#H}&mnBZ zhQ+G{26vSe%0G;7?kCt-j={bZ(kWg9$uBEQ`i+%NX=8h&Q($n{1k@Smtl4$qX&0ZW z^==&`2*UKf83S~P2@;lm7Z}{fQUMcA&P!z1wDcQ{Z^8T7S|vZ?W=Qca4qQX37jv>m zw3|Tt`~ud$ALP>#5Y>G0BKJU?W|SL%!M$D5U3tdkMiNME z?!FKP_fg61x(0bT27iET%mQF=Z!b(HE9#q%AasU~q{;IqjWUeb-%dfCEUJRA25c24s^YPI3eW_xVno36zh}xEny;sm*9n zw{L)f97Fs5f*Mv#2Sa2?6cvDo;ns_X@Kjlz+mv_BvtI-T_lYAE0G!8Yu-nQ9Q`;1< z0+{C`PXpcgWGA8WFjlYmN3PAD@|`orO6U$4HV$?ngW7LO?9AsM#NAucneHG0j@{0P zYI85~7-64l9-!6xAybjKjcr7Zcg$!~Vp+0DZfl?85aYP70)x9KaLMh~reto)&10`< zYxH4?7w4UP;xRh=+&hN1t*)s{IDWqY2KNX)>p)f1Wj9JB^)OY#)dfAdm!>)?Jz~uJ z9t8$>1CNi^9>;m%)3N9800wtafzuAv?Kibo+E51Bo}RNi?*#_;4ff4UuBe4Xt<6#- zH;(~>`)XlR9aTM&E^@L`O?D0A1TeUV8Ld(4&Bhx~pHMLH#D^>x3fXsosn->_|z4puoCXDMW zu>Zw;xdy(*X<%@>NZ7;fDY@ftKWRl#`EDe?4jA0^f3~D#Qd_j0BEP>tnH>`^PG1VP zp}Wq94-|e%Ce9wbTBQxJ50WF#e7~GSle|3M;{_eEpX2#1L405Ar zGKqiP)N-6g+?5HhwjPVYjU*4CoHRvz!@OHjYL@r*j21X!M5dW958{ro2cYR;K30Jj zqo$-r29V0L-;9Ir5vP4+-P>nK9#2rPNXI1|gq4i1eo0_(e{t8VB(O{rpPHKXB96wK z5m$1Uh^M!zu9;Ib<9RnAb!;!|C)xQf8`~^xP40A9h&=?@fRA`<*N*9uJ!1szyc~FJ zh~s5vwCk?bqiijye_0YvfapS~^6Ps&hyb!c%uKvjd2cVgd3NL-4?kLFgyY;Qr1m)VbnH;#rmuvD; zm57?hRWBdcu3cLn*Vf0i^-RWEb}r?@%}mE&GByJ>PM*Y=8Rc`Z{1E4x0M z_~f&Ia;|I`qAx-8>4540Cr$n^HK;>mIrDsmk(98->AJM0g^Ro?32%;kX^Lxt<1$GFMP>~l_rY69$9p1Hk{&MBZE`+=7!fuS+dpD_c$KN zVO}9n%6mpW&uJHSYs(Ezekgvm+%^uECP;E>%O=ImuoNlPx%`6+Wyu0O6woGqDxRFK zgZhduhmQq*$nhrVKgzCVmy-?-sa1>Jr7+;bU@`oXoFPX7lpFX@PjP}ID?1tlG*|YB zgcVvgDES`y0@xBIS1Jq%5F4(|p?W*{8!;KjRvy zgdCiO7Z70>^uW#^hDp9wpqnCgI7-*&UwpI9_NIqOI$v<3WEVJKwvZ@?mI~nzs>hcm z_&sE&bSzuw8TeTE6ekDar(9&WyVq>V9KZgi<^e-uJj|dIKYKdv=zKgudRvcX9nXwsE8cZ7*zm}- z!#vVblTHtfxgQ39gxzJnJy$AmW)PvZ3%Sya;mWiL?->{wXhRa!oIT*X)e}M>ZOYWS zUQ5_HA8D2GJ7l=|Z4MtUm!U8NGRv*uepk*Wx~wgBAITs?cECms!aLoSe&=6Ho^XCTj0E854oJ7fF}+d`tSVhrY;S#Fp8Ovj(6pGrC^#_ zzKUyhzkeX^Im7KI`c2M%u%B$`#Q97*Q!tD|CY>la96C>@qy}L19auL6mzIJfY;(9l zyQM-Vn=cfaZ7Vl6oS){@LVa1UwHNZ=JN6a5aPq${{_fLj!t1N;{{NxtSM)pK^KaV& zGoEjByDU&Un7j^#T2M`20$HXvXu6t`F)};q!0V!!w?5bbUy#44;3~9+~lc zqwB-^t?>Cb?D&l58(kmKZ-&qR*&dzoe531e{YLow>-N};=RfK7QT^wzofURsMmvqJ zkLlOL=U=mvGoJsX*Ase0*v_kVYDPPat|#?t;q$N9=^4*|((5VxYS_+ldwfPajjpHl zE8+7m+nE{9f70vYdU@E+OLlffJD>D=M!y`k^P-)b(atBmp4Bgf?Yv;;XSDN4ujllO zVLQw0!i;wQR@d|Tg|PkS?c$8~Kk4;?UKY0VC)=3O&fn^KQ9mEHztlEowEs!38~RVP zwo|*l#I|O%|F^ns>ZM`7f3!<8+W%W$f6mr3+PCzQu;2f$ub1>6!+sar_6&Od-@dN( zb78;F0?=P?Pyef2xAo$X{zV)rsHgwG%5_IS8`A%b?aiS7fA;lFw$l9#Xxd~?=z;&# zy^0=e_+C_(KIZPnz4@P`<~$?3VoR@$#!`Kv^0D{(X5o{o7lm`W8R*@s+n&D1&KBv~ zpE2!qw@d7)Z*==2Ej!FY(vFp^#;wR>oFmg&JIZ4PNG&wzugh+>in!^eiWMnoJ}j>% zc#qGm@cRV6Pn0;f$ZwGnmol;|heIDFpC3Z!($dK>h$CJb!ACY*1A#vQP{QEQkR>ff zfVI#Qyhelk%oPrx?@AEoX+_Hl=?LT=-A$CDbYkM%<|4t4unT@LPOH^4iu~KQ%ZzSb*ewV0r17#m!nb6&m%?N`|@^WMyWiUfN6nr$$n-MnFk<5xPx-$;NSYVho4yD8+rV1xS? z=*OTvmmdvnR;jn%bH-6%MteM;bU= zn&?)D1#Wy>W-`&>Wg+(Jd$r(S?j;)} z2CAz!^>KGgSKqBAEy8z8=>{o{@UHc-cN@O@E@BXRO;^8O%e#cqH{KvABTW7z_i9NY zF<0VSW*Q`oB=|@-xF=5a_*M66Nh`URX^_wo-vZ5?{kYrKU)`>y)vAt5Dc2zDCb#h* z8{HN@U45^X;H%0-zCrp=-n{x=Eyakm;jR`MBq0BzyA|SrnW@WKsuPOv6^jiLp;dZ6 z{;ts=87qpV(Y>n^xQ!CNjjn&vM02C(8zs0KU2l{?Z*;v;g1*u9Mg@RI*BccW8eMNx zz-V;6QGuk<^-o%uX;k28biG%v3xn-7cHd+qwNOhO+UR+Iec&v)yM;q~=)&;QW%kMySS`3?4^8P7Mm-laF&TZytR@zzGWd&WB#^%i@ZtWryt z1{&VCEEV3}WPillHUTw9W=qe9EeVG%=yz$Q_S9xut~nS+uyH0V&hnPlO+9*EZ?$)^ zcq??N(1J>vrLmGrO>B#Ok*8c5rwm#0EyD29h8gd@$J0UGdtHRHTPNQm>Mx}%*HqT@ zJDqx)y~kF$iZ$0P=F(uVmP$oa*zm4>A?z!1B}WQjAZq4vt{F{a&6QIf%FBE08;Mjn zou4dPRz6p>?P4z9Gxff$qsdXy5jPORr;_GHBbUrkv8-?9oZfElCxEA8%Sw->O>jI> zwja^PR{K1i*}4^+KW)Yf7b|9SYD+vhoP=w+X+k>m4*LM_eA+6GTe+N&gGX$Rj+))H zvd!)cM=~c#&c)1Wo$Ridb6-?TP0&ifC(6!j=pb=HG3Quwi^+7-DvghqtfUO6+6)8A zjHb!2n92!BCIe-Kku)ehGs9=1hisPWLm*_oiNBUNrL|r5X&#HDO}_4x>0Kij*=Mf! zIy#WZO%Ls)-oyLNV>mutchcQP%g&O%S!lm!f5_V^N!E?kpmu_jy|nx6mJ{#>RCO-y znWCXxzi+qO5i3vNbOfaAU8V6Ww6NPg#Um9RNu+rG1X$U9NOL+j%?o+)m>o@atpF|i z(Oka%t)_G#v+Wk}vR~{Xv_7^JS16?s=RO<#&Rey>!@0esm>ml@4Fk_VpxPOSWCe@MiY-mU}?Ye#%N4b&Y3r zVHEu^KwkF9cR3zEe}4kGQIX#+2B2n*YfY4nO|^McAAnxOr--()CW?`+AA+F$G649} z;6D2})HaO*pV5oNTIvnJ@BT^fUImK#pTN*wu>3^EFwA0}WNG_Ut4H()v^tj$9Ybl3 zwtfSO_DU-sHoD(FMx!We9V?iF`y^)m3*`AEf0uq&SQD+!d4xNZRi+2}11w`>1|Kx8%}V58k4{DM?z{Y|cpMDn=yJ z&9vPDmiGFrZ#HGqW~n4=wA$z)eaP;y=UEV2%8vG1`S$xjhA-F-PW= zfofj$D`n){1( z>C#g$_}iR~{_a^U=-5YJ23gx;-Q`(ZMTT|AK1839bOJ4CFhi@^yQG@mx?&kgT8#N{!u3QT6 z#<#aI$Ra<2uKfl64m}Sc;IMrV0-_jJ)MecKw-8_ZmQ=->$ROB8QBN3n2Dy_Dg0H9k6^!jIEPyye zhYtl>#Xj*Z1Z|1~B#Y73%?1IaRjlYwBcueAr2VNJ@CXR7=)3~|%bB8<*Va)yz&b}K zV`W+%|MMvb#%qe2BX>gaH`c~2D7L04R@kpd&+8%1_Lq5X0K;LajF20HrUGlp$QF_( zQMlXUbowc)uNMV-Mr;{p2B%w=}} zZ;P0hh><93Rj84>Pan4j?TE=tN3QG>r0snjY0DhDA|&Tcfaqf{Ua*A8A<0MW1I%!9 zfu)nXGsN27k}DJ{R+A~;Q5Tx8H?rwWj>Q=|Z-uj9ZQl%EE%V%Ke_*6@a%;9An!L|H znrJ7HDeF1L;NG}SJQPv$Z#bv*$tNCJzVqK6`}x0swf!$;eckYhy?emgKG2iYS5}{6 zUJBv@iYPpwUwKTaV|w6ZK_~V3gWMP$ zhRevymRppX58n3D-uV6CZEr!G3~T))jqmGr`-&YIe*id^TMnSmOxud)t~>zZwiO-k zPt{m^+}@{`=t%NGFt=}1YOcBuwJH#kxojv8EFjms9^VhM8#88uUA=cb2|!emn-XByU7(SN?U8M!qf4)+OM&3dB#qR%3Ak)bbu z#C^}GRZJ(*O;EwU>1~+M0r4g;XkcR-^ZIOJTTRCiX54F|WUC*mdmMp*xthX!7hs zpmDD_J2;xoiz1m?5LH%P2!u3KTvs$8#bhI&NhkZ`=*O6u9mfvdm>cS~EQxB9#{p2z z+lK^=`*+*yGxkos9NEpDoDUxN{6e-OTG~ja3LUyR)papZD3r}e?nFr2Evb6iw(E0D zWprr6Pw~~ZNSz)BGL2Bx&!XwWDJYW}hHKZG<0F?ZbR0M>!IGK7ip0h`kK73&w|&-2H&LvA;d1u9UL5J!b#}HY`-FAn)L;soZE1lITj0K;jIf!P)s+`dC ztel}Oeg%L3BDmZ){#zB7`=5Vu=biWLc=6!}m+m}uwph15x9an@i?58}*$7w(oFN-z z+C!(tCXJlknzz#U`WCt<_WL2L7i8{loGXaImwgYPDH%C4eTrpe_7COk{dS1V zeI+l#oZW4ov$qSiR>1)K{S4HeOF?ZqW1M9E(IjO)SfF+g7{B-vQAr;GFgEWCCyiWR z@2Hu%GFYhaDVu41@~*xBy$V*%9cRwFSjD+3FQbm0CX<7l!E4ik?ZxDy4CCo>FjoKW z<+PPVR~W^T760hu<~Ntzsnkzjd>};U&f8{|f$z3E?QNpp&ixasWlvfBivkozQs_HY z9aKavegPuy`DgICU+y}$1FLSnuGA0D^!D{~H}>A%lfz|GJ%(TXM4@D=LOs=6^hMjT zn?&<5iLpzjy%>b4`)ng-}NdYryFK{QgWXk!Fv3CTA2zd+Q0`th*q5ZVoP&?7YYbDBVw8 zKpu)@RiM=jm6G`O`#|a5efmoK&uS>$H;;=@+3@y){jN}qwTdKO2B-V23_e#nB)Ns% z6WY9ZKQ`y{V5j!rgceS3TD|6tW%nE*!IViFCeVrIswJeGnviQF<(iQ*H-giB--5?q zI}$Gh;n?eG-lRVgn)9|zrRS;LN&6I%+li)kER_$*9f#a4AyW4{7^Xb|+xJR@FF*}UO=Uor;(x|uLY%cZ+}%jsAq zo6QhhX0ONhGt9wf)IB|B+%yUvO^F(*Cv>CkhR``h+Z?wK;U?Y3;&=yEM#i#EC=E?T z)W_N>wC?R_2DsT}Z-&&Sl%aEAaK^gLTs)N5OtxLOW>fe914lj&UiYahL~Okq!RscV zNfe9>aCgm_B-|8rhK$4}FHt0(%v=*MiDPKy6_f>27++3m+R(A&QrMd; zZL0(1Zd3=z-5Wj{!&pJX9{ZBLL3%7$r=9`3dpToF?7hNh(+O!i!e^ZbNU_h^HvrWB z!m&zShimm^I*g%mVPR8$F=w3~vRkdOB7LQw4$-?G{we6)k702K3AV&8?jcZK9}dwx zaSvY7X(G%FQB)S=)`{`SMk;lmltJL%e*XTYN8XL6jw~^3WKfpr@dnC)8 zFIpoZg7;5P+V#Dt`UdLL`+YVd#@FMK&?bP#xt*oAc z`Njg0In8?wGo6B(S|Z^cHapU7zin?JnhxVcD>sDD#qxwMA&U36mmRrs78LKtH(fD1 zmOg#NN+!3IQgqg5*8_q2Isw%F9chQn<~{KmE4)#^TZGo#&Hd*;3iQDLIBR%4ZK;wsS}vt0!C9W%t=@gd`Zy4RW?P9tK^DrHP@7 z(OE4^`^F_Ash|6|*)!~UqGXGe=;>3~eu7&*QoH3yzj#XTexR_j#~;hQP}Rga})HFj#> zj2ylhaMOPouyT1q-%^1%qc%?~Wm^(Hn7t1zaV0xuU4kfrIsRmigXq2Nm1QfI+_w?$ z*(_yc#o@-3>>H3@%*u#(9r8;)u>XPeS{xTclg;;Ac0QRenIGY=GX_>y_}cg$d%%7h z5}Qhe;p{~~9{MgX>;pU5!W5;;xdTkQOS#?tr}P`xdv8_(u*IWu9m zl3|lY`hg^DK=nR!rHFzk5~M67d|nmygp}SliR-=4Y)NRMWSzrZl`m%{FlkB79TFKQ zp9FY%{vo(Cor-*uT{|}E4s!4dm?WS23Smgj$`x~C`L)d95o#CLJ#@5awQpGR8<4&K zj|)^h#7r$D$+S|s#{`(G$aj91kjENMr+T^w`dBw~%@WqFMoyc(1RY>cZbrMEn3|V2 z*cC+INzlE2Z;kn6GII27GW7Jgh5R7VhaTEKIPO|a!@JjRPkCtch4!%hmVR5elq?c{ zeUF0gy`~G-_)0+T3dc&%W4ob|Olx!0IPpWU2K%hi@u?nPXTwaWD9lA5e18F_RwN7d z;;$DiiPe7(zM*Md?#NfH8}frE#Q`>xLj$C5NhF2+$L%-hAIF`LK*a^)d#N5!|C~(> zRfCosSt!%&g4Ms>);?C3OJ2by7uRk=U0hlQ%J=rmOoYWDKNud(N$V<2n4GryuMk?| zZb1#TocctpOT+YrGm*SR9=(+{8&?Q;BXePzZrWq``+&Lpz2?fK6o(vQh z6283}_B+qS;$0zF!OCYbKuL3O@#jO#?^{-93Upi`EbCfME`7O~WS!^p(}R7PeKQ0u zMbyYgZ*LpSjjhDMEbS!pq|8=CN0*XgOoGZuI4$$7Rsy(fpkM;UuvyykjO=LIL?jXe!)w6(Ue!ejO3KF5)V#z#9DGi3cRPA1 zdyOmt`TOywuM~@8$#Jr1$bS+&77@x6U+ah_?L35(ibBY^y zL3YMg^KSg7jaW;ak=!eFQwYT~;6poR-&ZNBEEz=)p`zA&wo#{#BR$*hjB`0zUsZb>%!2Y8^U;$gH4%-^t2>5X{m-BAyq75Vp^T%pe*h z#cDKDr%k{3!w?62=NW7PNfsb!=b)$!GCyWvKc;f$6@nZHr)Y6xYK1Ok4hl4A$-O=7vHQ_h!1T;4C}=lzXf7ehPJO-!zkL%>vf`;Yt&?jR-(C77Tvro z!~%cZq+me0o&-e-lO110N#YMZ62KmqB4G(<{)={7b|{&}I~_RyhVE{3l}XyE+Hdi@ zsP$gkOih+f4Es;jZW&9dRM2y>!ed5uo}yf2yWoMJlB9LU(JknYs_symrc0{Nu>;?= zKQl_4gKf91Uizi9XsuoJ<}HBezKLtXx_}|(PYGvU#Nf-;3CmuB^&=HW7A@&h4bhsC z%$CUMo52KMrKye3VY6urjG8Gh!RHtBa9ZaI`=$oEnA8gMPX2u)U)%^P_@kpX%pJ$c zz@+B##TAV6@m)#T-B7as$a@hpK_dDYZDyqEGAD_on|xc&6j zb;SaKDj`)!NGyPczSN3VLa5@gX@Hgm1yP_y5rVc!C1{)Uoz{JXBu<>zW6yYI?!7a% z6FdCQID;pK{{Sme)7+VR?!D)}&-a}(FQ2|KUlfW!6dfb)psjw{zJmkyUa>)|Dz-s3 z*)eQFZxb7QrwzW|E#e5S7g9t908v{PB|7-GE0_=}w!Y7?|KR z^PHlO{D!5u%7mP~qa3Cd-d_Jqx;L^lbk4_LFe8g_yPP4?y`8wyI z3)9GLMn^=hnxjMS5F?!XFZZ_PQ@eIi2q+S}W*725043AM!^2XvUrChkMaKk~_y(xxDcdtYq%yR)DrHra-%I51Z^$yAk3PgW zCs`U;3fyq&Rbq!9vf234*;cqsY~t))nx4LTZV%DJzkqwh>^q3VPbY6qhyu*@-NbspBCbC;#zaI)s(}90U6GM)_)RF{B~qxdy?^)cr(|*AOj)vtf@@-YhsF@ z{>&9Ps53D4hczw=jP-pih?nKW=;tHh92eHv*FMM8VpW}G>FkLq#lPf!a-s@fUAg zyEUf;drL$tbBql2(89c5o4NTuOVBww0vcUDN}?O!c&4(!_`5F=XB^amEy$cn2`F;@ z!c9*Puqw*^To*s#xVi+oW*$UIWClOnj-i8AU{;wuv-NovNLPQAl2YK*LJyg`&iQOm zl|4iof0Lt@Al8hmBoc)*X0hx!<)EoD!Nh1v!W@XII09z%(i{~T;{HB2`Z0U_@V;Fq zeaZvyvltdh&Chy$klny@=XTlnCZbTi1lE7F@TXu4qJ zz3Bp~3NqT8E&!TfT)pXHPZw)=Z@L%}#U$99E_MqsL-eM9inl>2`utz%va@$~?j(nB z0eXM>X~{U)sF}WA`g)NO(H{qW9rVXRXKn1yh5lUV&xQV6=xCrn7y5IdKNtFQA<;m8 zJ?O6o|DW|>8e>^OPlX4eV7H9G4(Z;1fZeZv8Eq=W3iBV&J8y5yvoUQn_GLERc(lpm zX_!JSu!ROu#xlO!#AN65!NfM)?^;+lzxG&ortzw(n+mn8V9A7uvj~fi-_Yx@WvUg3 zjkwmBak3Stbe>92;uO~~t407C`I!zc0~^+amk|!aR4DTSNMGM0C!}3$et^|0M*Pu> z2)K_^-(+}{GSdPAo?uPIQwVJkq%8IZ8U(=Bu&Tk6K;~ev1TDO#C5@^<2VMa`a&gX< z3PN%As^v1CM3_?L0p2t)e|c?_96xr{e>U9SaAJrFp2rI}2aid{EAEKYvM&=V=Mr_5 zlEJVyRb2s!V~5`XsHRPL9uOUIl7)F8f#}E(ZU^^`p@Ms7th@6+UM=0qC?>s(|MJ;6chm5ICT?DBfu9gR!rlprsSxHsj+E;;EXw RTq@gk&UZ`mGv%(P{su+b#pD10 literal 0 HcmV?d00001 diff --git a/asset/arrowheads-scan.psd b/asset/arrowheads-scan.psd new file mode 100644 index 0000000000000000000000000000000000000000..cb39c8308b7608110f7f37e0bb44643cf79bc268 GIT binary patch literal 926295 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

dOf{Cjp_Fosq%;sy(_WS>hB?_hFm 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

M~D;gD7W3#&TdS@FyUdMxG9Z@r01c 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%0Am(BgDvMC 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 V?{^oC`?Hs?@|^#jyNAs6{|Ce3u`d7s literal 0 HcmV?d00001 diff --git a/asset/object-pool-heap-fragment.psd b/asset/object-pool-heap-fragment.psd new file mode 100644 index 0000000000000000000000000000000000000000..1e3d63d3c4845893bdd2a4378a76c73f74453cab GIT binary patch literal 805816 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 literal 0 HcmV?d00001 diff --git a/asset/process.txt b/asset/process.txt new file mode 100644 index 0000000..28c32c7 --- /dev/null +++ b/asset/process.txt @@ -0,0 +1,8 @@ +1. Desaturate. +2. Adjust the bottom end of the levels a bit to get the darkest to just be black (but no other grays). +3. Unsharp mask 90% 3.5 px 7 threshold +4. Now adjust the levels again to make the pen lines mostly solid black. Bring up the mids a bit so the grid lines aren't too dark. +5. Rotate about 1° CCW. +6. Add the yellow overlay. +7. Scale and crop to 1040 wide. +8. Save as 8-bit PNG with diffusion dither and 32 colors. \ No newline at end of file diff --git a/asset/style.scss b/asset/style.scss new file mode 100644 index 0000000..6bb3adf --- /dev/null +++ b/asset/style.scss @@ -0,0 +1,607 @@ +$primary: hsl(200, 80%, 40%); +$serif: "Merriweather", Georgia, serif; +$mono: "Source Code Pro", Menlo, Consolas, Monaco, monospace; +$sans: "Source Sans Pro", "Lucida Grande", "Lucida Sans Unicode", + Verdana, sans-serif; +$header: "Source Sans Pro", Georgia, serif; + +$hairline: #eee; + +body, h1, h2, h3, h4, p, blockquote, code, ul, ol, dl, dd, img { + margin: 0; +} + +body { + line-height: 24px; + color: #222; + font: normal 14px/24px $serif; + + background: hsl(40, 10%, 90%); + background-image: url("images/background.png"); +} + +nav { + background: hsl(200, 50%, 96%); + + color: hsl(200, 20%, 60%); + font: 14px $sans; + margin: 40px -38px 0 -38px; + padding: 10px 38px; + + .toc { + display: block; + width: 150px; + text-align: center; + margin: 0 auto; + } + + .prev { + float: left; + } + + .next { + float: right; + } +} + +nav.top { + margin-top: 0; +} + +.page { + background: hsl(40, 30%, 98%); + max-width: 640px; + margin: 0 auto; + + border-left: solid 1px #bbb; + border-right: solid 1px #bbb; + border-bottom: solid 1px #999; + + // So that asides are positioned relative to it. + position: relative; + + box-shadow: 0 0 40px hsla(30, 40%, 20%, 0.2); +} + +.content { + padding: 0 40px 2px 40px; + + background: white; + box-shadow: 0 0 30px hsla(30, 40%, 20%, 0.05); +} + +// Note: We use explicit line heights here so that the positions of the +// elements don't change after the fonts are loaded asynchronously. That way, +// when we position the asides procedurally, they are in the right place. + +h1 { + font: 200 60px $header; + padding: 100px 0 4px 0; + border-bottom: solid 1px $hairline; +} + +h1.book { + color: #777; + font: normal 16px/20px $header; + padding: 6px 0 30px 0; + border: none; +} + +.section:before { + content: " / "; +} + +h2 { + color: $primary; + font: 300 35px $header; + margin: 20px 0 15px -38px; + padding: 0 0 2px 30px; + border-left: solid 8px hsl(200, 50%, 90%); +} + +h3 { + color: $primary; + font: 400 20px $header; + margin: 20px 0 0 -38px; + padding-left: 30px; + border-left: solid 8px hsl(200, 50%, 90%); +} + +p { + margin: 15px 0; +} + +ul, ol { + padding: 0 0 0 24px; +} + +a { + color: $primary; + text-decoration: none; + outline: none; + + border-bottom: solid 1px hsla(200, 80%, 90%, 0); + + transition: color 0.2s ease, + border-color 0.2s ease; +} + +img { + max-width: 100%; + + border: solid 1px hsl(40, 30%, 95%); + border-radius: 3px; +} + +// Hackish way of scaling it down for retina. +img.arrow { + vertical-align: middle; + margin-top: -2px; + width: 30px; + height: 20px; +} + +img.profile { + width: 150px; + float: right; + margin: 0 0 0 15px; + padding: 10px; + + background: hsl(40, 35%, 93%); + border: solid 1px hsl(40, 40%, 90%); + border-bottom: solid 1px hsl(40, 40%, 85%); + border-radius: 3px; +} + +blockquote { + padding: 10px; + background: hsl(200, 20%, 98%); + border: solid 1px hsl(200, 20%, 96%); + border-bottom: solid 1px hsl(200, 30%, 90%); + border-radius: 3px; + + p { + font: italic 14px/24px $serif; + color: hsl(200, 10%, 50%); + } + + p:first-child { + margin-top: 0; + } + + p:last-child { + margin-bottom: 0; + } +} + +code { + color: hsl(200, 20%, 40%); + font: normal 15px $mono; + white-space: nowrap; +} + +a code { + color: $primary; +} + +pre { + color: #444; + font: normal 13px/16px $mono; + padding: 10px; + + background: hsl(200, 40%, 98%); + border: solid 1px hsl(200, 40%, 96%); + border-bottom: solid 1px hsl(200, 50%, 90%); + border-radius: 3px; + + // Scroll horizontally if not wide enough. + overflow: auto; +} + +td { + vertical-align: top; +} + +// Asides. + +aside { + color: hsl(40, 5%, 50%); + + // These are only used when the asides are inline with the text. + padding: 10px 10px; + background: hsl(40, 30%, 97%); + border: solid 1px hsl(40, 40%, 95%); + border-bottom: solid 1px hsl(40, 40%, 90%); + border-radius: 3px; + + h2 { + color: hsl(40, 5%, 60%); + font-size: 24px; + border: none; + margin: 20px 0 10px 0; + padding: 0; + } + + p, li { + font: 15px/22px $sans; + } + + li { + line-height: 20px; + } + + >p:first-child { + margin-top: 0; + } + + >p:last-child { + margin-bottom: 0; + } + + ul, ol { + padding: 0 0 0 18px; + } + + code { + color: #666; + font: normal 13px $mono; + } + + img { + padding: 0; + } +} + +aside + aside { + margin-top: 10px; +} + +// The front page gets a bit of special styling. +.index { + h1 { + border: none; + } + + aside { + display: none; + } + + // No little tab boxes next to the headers. + h2, h3 { + border-left: none; + margin-left: 0; + padding-left: 0; + } +} + +.rhetorical { + font: 16px $sans; + font-weight: 400; + + background: hsl(200, 80%, 95%); + color: hsl(200, 30%, 40%); + border-radius: 4px; + margin-top: 20px; + padding: 20px; + + border: solid 1px hsl(200, 85%, 90%); + border-bottom: solid 1px hsl(200, 85%, 85%); + + h2 { + font: normal 19px/30px "Rock Salt", $sans; + color: hsl(200, 80%, 50%); + margin: 0 0 10px 0; + padding: 0; + } + + li + li { + margin-top: 10px; + } +} + +a.start-reading { + display: block; + width: 40%; + margin: 20px auto; + padding: 8px 0; + border-radius: 4px; + background: hsl(200, 80%, 45%); + border: solid 1px hsl(200, 85%, 35%); + border-bottom: solid 3px hsl(200, 85%, 35%); + box-shadow: 0 0 0 hsl(200, 100%, 70%); + color: white; + font: 300 18px $header; + font-weight: 400; + text-align: center; + + transition: background-color 0.2s ease, + border-color 0.2s ease, + box-shadow 0.2s ease; +} + +a.start-reading:hover { + background-color: hsl(200, 85%, 55%); + border-color: hsl(200, 85%, 40%); + border-bottom: solid 3px hsl(200, 85%, 40%); + box-shadow: 0 0 10px hsl(200, 100%, 80%); +} + +// Sign-up box on front page. +.sign-up { + background: hsl(40, 90%, 95%); + + margin: 0; + padding: 5px 15px; + font: normal 16px $sans; + color: hsl(40, 50%, 50%); + + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + + border-left: solid 1px hsl(40, 80%, 90%); + border-right: solid 1px hsl(40, 80%, 90%); + border-bottom: solid 1px hsl(40, 80%, 80%); + + p { + margin: 5px 0; + } + + h2 { + font: normal 19px/30px "Rock Salt", $sans; + color: hsl(40, 80%, 55%); + border: none; + margin: 10px 0; + padding: 0; + } + + small { + color: hsl(40, 50%, 70%); + line-height: 14px; + } + + // Based on MailChimp Form Embed Code - Slim - 08/17/2011 + #mc_embed_signup input { + border: 1px solid hsl(40, 50%, 75%); + border-radius: 4px; + outline: none; + font-size: 13px; + -webkit-appearance:none; + } + + #mc_embed_signup input:focus { + border-color: hsl(40, 100%, 40%); + } + + #mc_embed_signup label { + white-space: nowrap; + } + + #mc_embed_signup .button { + background-color: hsl(40, 100%, 55%); + border: solid 1px hsl(40, 100%, 45%); + border-radius: 4px; + border-bottom: solid 3px hsl(40, 100%, 45%); + color: #fff; + cursor: pointer; + font-size: 13px; + text-align: center; + text-decoration: none; + vertical-align: top; + width: 100%; + padding: 4px 16px; + + box-shadow: 0 0 0 hsl(40, 100%, 65%); + + transition: background-color 0.2s ease, + border-color 0.2s ease, + box-shadow 0.2s ease; + } + + #mc_embed_signup .button:hover { + background-color: hsl(40, 100%, 65%); + border-color: hsl(40, 100%, 50%); + box-shadow: 0 0 10px hsl(40, 100%, 65%); + } + + #mc_embed_signup .small-meta {font-size: 11px;} + + #mc_embed_signup input.email { + padding: 5px 0; + text-indent: 5px; + width: 100%; + } + + // Generic Mailchimp stuff. + #mc_embed_signup div#mce-responses {float:left; top:-1.4em; padding:0em .5em 0em .5em; overflow:hidden; width:90%;margin: 0 5%; clear: both;} + #mc_embed_signup div.response {margin:1em 0; padding:1em .5em .5em 0; font-weight:bold; float:left; top:-1.5em; z-index:1; width:80%;} + #mc_embed_signup #mce-error-response {display:none;} + #mc_embed_signup #mce-success-response {color:#529214; display:none;} + #mc_embed_signup label.error {display:block; float:none; width:auto; margin-left:1.05em; text-align:left; padding:.5em 0;} +} + +// Pattern links. +a.pattern, +a.gof-pattern { +} + +a:hover { + border-bottom: solid 1px hsl(200, 80%, 90%); +} + +a.pattern:after, +a.gof-pattern:after, +a.pdf:after { + font: 9px $sans; + color: mix($primary, #fff, 50%); + vertical-align: super; +} + +a.pattern:after { + content: "\00A0\2192"; +} + +a.gof-pattern:after { + content: "\00A0GoF"; +} + +a.pdf:after { + content: "\00A0PDF"; +} + +// Footer. +footer { + max-width: 640px; + margin: 20px auto; + + text-align: center; + color: #aaa; + font: 14px $sans; +} + +.narrow-sign-up { + border-radius: 4px; + border-top: solid 1px hsl(40, 80%, 85%); +} + +// Syntax highlighting. +.codehilite .k { color: $primary; } // keyword +.codehilite .kd { color: $primary; } // keyword declaration +.codehilite .kt { color: #09a; } // keyword type +.codehilite .s { color: #c70; } // string +.codehilite .sc { color: #c70; } // string +.codehilite .s2 { color: #c70; } // string +.codehilite .c { color: #999; } // comment +.codehilite .c1 { color: #999; } // comment +.codehilite .cm { color: #999; } // comment +.codehilite .cp { color: #594; } // preprocessor +.codehilite .nl { color: $primary; } // keyword label (private: etc.) +.codehilite .nt { color: $primary; } // json key + +// Hack: Pygments isn't recognizing preprocessor directives, so just color the +// error like a keyword. +.codehilite .err { color: $primary; } + +// On phone-size screens, shrink all of the text a bit. +@media only screen and (max-width: 479px) { + .content { + padding: 0 20px 2px 20px; + } + + h1 { + font-size: 50px; + padding-top: 70px; + } + + h1.book { + padding-bottom: 20px; + } + + .section { + display: block; + } + + .section:before { + content: ""; + } + + h2 { + font-size: 25px; + margin-left: -18px; + padding-left: 10px; + border-left: solid 6px hsl(200, 50%, 90%); + } + + h3 { + font-size: 18px; + margin-left: -18px; + padding-left: 10px; + border-left: solid 6px hsl(200, 50%, 90%); + } + + p, li { + font: normal 13px/22px $serif; + } + + blockquote p { + font: italic 13px/22px $serif; + } + + code { + font-size: 14px; + } + + pre { + padding: 6px; + font: normal 12px/16px $mono; + + // Scroll horizontally if not wide enough. + overflow: auto; + } + + ul, ol { + padding: 0 0 0 20px; + } + + aside { + p, li { + font: 14px/22px $sans; + } + } + + nav { + margin: 20px -18px 0 -18px; + padding: 6px 18px; + font-size: 13px; + } + + footer { + font-size: 13px; + } +} + +// On wide enough screens, put the asides on the... side. +@media only screen and (min-width: 800px) { + .page { + padding-right: 300px; + } + + aside { + position: absolute; + width: 240px; + right: 30px; + + // Clear out the inline aside styles. + padding: 0; + background: none; + border: none; + + padding-left: 22px; + border-radius: 0; + border-left: solid 8px hsl(40, 40%, 90%); + + p, li { + font: 14px/20px $sans; + } + } + + + // The front page gets a bit of special styling. + .index { + // Don't highlight the aside. + aside { + display: block; + border-left: none; + } + } + + // Normally, show the chapter list in the sidebar, so don't show it here. + .page { + .narrow-toc { + display: none; + } + + .narrow-sign-up { + display: none; + } + } +} \ No newline at end of file diff --git a/asset/subclass-sandbox-coupling.psd b/asset/subclass-sandbox-coupling.psd new file mode 100644 index 0000000000000000000000000000000000000000..00c33b63580225f2d7ef288aa1aed2b401e4eb74 GIT binary patch literal 307634 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 PX7Z`dY_3?{{>A?v#x=Mw literal 0 HcmV?d00001 diff --git a/asset/template.html b/asset/template.html new file mode 100644 index 0000000..272c7e7 --- /dev/null +++ b/asset/template.html @@ -0,0 +1,45 @@ + + + + + +{{title}} · Game Programming Patterns + + + + + + + + + + +

+
© 2009-2015 Robert Nystrom
+ + diff --git a/asset/template.xml b/asset/template.xml new file mode 100644 index 0000000..ddfb22d --- /dev/null +++ b/asset/template.xml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + +{{body}} + diff --git a/book/acknowledgements.markdown b/book/acknowledgements.markdown new file mode 100644 index 0000000..7200c83 --- /dev/null +++ b/book/acknowledgements.markdown @@ -0,0 +1,77 @@ +^title Acknowledge­ments + +title 鸣谢 + +I've heard only other authors know what's involved in writing a book, but there +is another tribe who know the precise weight of that burden -- those with the +misfortune of being in a relationship with a writer. I wrote this in a space of +time painstakingly carved from the dense rock of life for me by my wife Megan. +Washing dishes and giving the kids baths may not be "writing", but without her +doing those, this book wouldn't be here. + +我听说只有作家知道在写作中会牵扯到什么, +但还有另外一群人知道内情——那些不幸与作家有关系的人。 +我的妻子从岩石般致密的生活中, +为我开凿出写作的时间。 +洗盘子,给孩子洗澡也许不是“写作”, +但没有她做这些,这本书永远没法写出来。 + +I started this project while a programmer at Electronic Arts. I don't think the +company knew quite what to make of it, and I'm grateful to Michael Malone, +Olivier Nallet, and Richard Wifall for supporting it and providing detailed, +insightful feedback on the first few chapters. + +我在EA当程序员时开始了这个项目。 +我认为公司并不知道这件事。 +我要感谢Michael Malone, Olivier Nallet, 以及Richard Wifall。 +他们为书籍的前几章提供了详尽的有价值建议。 + + +About halfway through writing, I decided to forgo a traditional publisher. I +knew that meant losing the guidance an editor brings, but I had email from +dozens of readers telling me where they wanted the book to go. I'd lose +proofreaders, but I had over 250 bug reports to help improve the prose. I'd give +up the incentive of a writing schedule, but with readers patting my back when I +finished each chapter, I had more than enough motivation. + +在写作的途中,我决定放弃传统的出版方式。 +我知道,这意味着没有编辑的指导,但我有成打的读者告诉我他们希望书籍是什么样的; +这意味着没有校对,但我有超过250个错误报告来帮助我改进文章; +这意味着没有写作期限的激励,但当我完成一章,我的读者会拍着我的背鼓励我,我会有更强的动力。 + + + +They call this "self publishing", but "crowd publishing" is closer to the mark. +Writing can be lonely work, but I was never alone. Even when I put the book on a +shelf for two years, the encouragement continued. Without the dozens of people +who didn't let me forget that they were waiting for more chapters, I never would +have picked it back up and finished. + +他们称之为“自出版”,但是“群出版”更加接近事实。 +写作也许是件孤独的事情,但我从来没感到孤独。 +哪怕在哪怕是我停止写作两年后,仍有人来激励我写下去。 +没有那些告诉我他们需要更多章节的人,我永远不会继续写下去。 + + + +To everyone who emailed or commented, upvoted or favorited, tweeted or +retweeted, anyone who reached out to me, or told a friend about the book, or +sent me a bug report: my heart is filled with gratitude for you. Completing this +book was one of my biggest goals in life, and you made it happen. + +那些写过邮件或者发过评论的人,那些点过赞或者喜欢的人,那些发过推特的人,那些与我交流的人,那些向朋友宣传这本书的人,那些向我发送错误报告的人,我要对你们说:我的心中充满了对你的感激。完成这本书是我人生中最大的目标之一,是你让我梦想成真。 + +Thank you! + +谢谢! \ No newline at end of file diff --git a/book/architecture-performance-and-games.markdown b/book/architecture-performance-and-games.markdown new file mode 100644 index 0000000..9feb098 --- /dev/null +++ b/book/architecture-performance-and-games.markdown @@ -0,0 +1,644 @@ +^title Architecture, Performance, and Games +^section Introduction + +Before we plunge headfirst into a pile of patterns, I thought it might help to +give you some context about how I think about software architecture and how it +applies to games. It may help you understand the rest of this book better. If +nothing else, when you get dragged into an argument +about how terrible (or awesome) design patterns and software architecture are, +it will give you some ammo to use. + +在我们一头扎入模式之前,我想先给你讲一些我对软件架构和其在游戏上的应用的理解这也许能帮你更好的理解这本书的其余部分。至少,在你被卷入一场关于软件架构有多么糟糕(或是多么优秀)的辩论时,可以给你一些火力支援。 + + + +## What is Software Architecture? + +## 什么是软件架构? + +If you read this book cover to cover, you won't come +away knowing the linear algebra behind 3D graphics or the calculus behind game +physics. It won't show you how to alpha-beta prune your AI's search tree or +simulate a room's reverberation in your audio playback. + +如果你把这本书从头到尾读一遍,你不会遇到在3D图形背后的线性代数或者游戏物理背后的微积分。 +这书不会告诉你如何用α-β修剪你的AI树,也不会告诉你如何在音频中模拟房间中的混响。 + + + +Instead, this book is about the code *between* all of that. It's less about +writing code +than it is about *organizing* it. Every program has *some* organization, even if +it's just "jam the whole thing into `main()` and see what happens", so I think +it's more interesting to talk about what makes for *good* organization. How do +we tell a good architecture from a bad one? + +相反,这本书告诉你在这些*之间*的事情。与其说这本书是讲如何写代码。不如说其是讲如何*组织*代码的。每一个程序都有*一定*组织,哪怕是“将所有东西都塞到`main()`中然后看看发生了什么”,所以我认为讲讲什么组成了*好的*组织。我们如何区分好架构和坏架构呢? + +I've been mulling over this question for about five years. Of course, like you, +I have an intuition about good design. We've all suffered through codebases so +bad, the best you could hope to do for them is take +them out back and put them out of their misery. + +我思考这个问题五年了。当然,像你一样,我有关于好设计的直觉。我们都被代码库折磨的不轻, + + + +A lucky few have had the opposite experience, a chance to work with beautifully +designed code. The kind of codebase that feels like a perfectly appointed luxury +hotel festooned with concierges waiting eagerly on your every whim. What's the +difference between the two? + +只有很少的幸运者有过相反的经验,有机会与好好设计的代码库一起工作。那种代码库看上去是一间豪华酒店,里面的门房随时为你的心血来潮负责。这两者之间的区别是什么呢? + +### What is *good* software architecture? + +### 什么是*好的*软件架构? + +For me, good design means that when I make a change, it's as if the entire +program was crafted in anticipation of it. I can solve a task with just a few +choice function calls that slot in perfectly, leaving not the slightest ripple +on the placid surface of the code. + +对于我来说,好的设计是当我改了点什么,整个程序就好象构造成恰好能够期待这种改动。 +我可以通过加入几个函数调用来完成任务,同时丝毫不改变代码平静表面下的脉动。 + +That sounds pretty, but it's not exactly actionable. "Just write your code so +that changes don't disturb its placid surface." Right. + +这听起来很好,只是完全不可行。“把你的代码写成改变不会影响其平静表面就好。”这样就行了。 + +Let me break that down a bit. The first key piece is that *architecture is about +change*. Someone has to be modifying the codebase. If no one is touching the +code -- whether because it's perfect and complete or so wretched no one will +sully their text editor with it -- its design is irrelevant. The measure of a +design is how easily it accommodates changes. With no changes, it's a runner who +never leaves the starting line. + +让我们通俗易懂些。第一个关键点是*架构是有关于变化的*。总有人的改动代码库。如果没人碰代码——无论是因为代码至善至美还是糟糕透顶以至于没人会为了修改它而玷污自己的文本编辑器——那么它的设计就无关紧要。评价设计就是通过评价它应对改变有多么容易。没有了改变,那就是一个永远不会离开起跑线的运动员。 + +### How do you make a change? + +### 你如何做变化? + +Before you can change the code to add a new feature, to fix a bug, or for whatever +reason caused you to fire up your editor, you have to understand what the +existing code is doing. You don't have to know the whole program, of course, but +you need to load all of the relevant pieces of it into +your primate brain. + +在你改变代码去添加一些新的特性,去修复漏洞,还是随便什么需要你使用编辑器的时候,你需要理解你现在的代码在做些什么。当然,你不需要理解整个程序,但你需要将所有相关的东西装进你的灵长类大脑。 + + + +We tend to gloss over this step, but it's often the most time-consuming part of +programming. If you think paging some data from disk into RAM is slow, try +paging it into a simian cerebrum over a pair of optical nerves. + +我们通常无视了这一步,但这往往是编程中最耗时的部分。如果你认为将一些数据从磁盘上分页到RAM上很慢,那么试着通过一对神经纤维将数据分页到猴子的脑子里。 + +Once you've got all the right context into your wetware, you think for a bit and +figure out your solution. There can be a lot of back and forth here, but often +this is relatively straightforward. Once you understand the problem and the +parts of the code it touches, the actual coding is sometimes trivial. + +一旦我们把所有正确的上下文都记到了你的湿件里,你想了一会,然后找到了你的解决方案。这可能会有些来回打转的时候,但通常是比较简单的部分。一旦你理解了问题和需要改动的代码,实际的编码工作有时候是微不足道的。 + +You beat your meaty fingers on the keyboard for a while until the right colored +lights blink on screen and you're done, right? Not just yet! Before you write +tests and send it off for code review, you often have +some cleanup to do. + +你用你的肥手指在键盘上敲打一阵子直到屏幕上显示着正确的颜色的光芒,然后一切就完成了,对吧?还没呢!在你写测试并将其发送该代码评审之前,你还有些清理工作要去做。 + + + +You jammed a bit more code into your game, but you don't want the next person to +come along to trip over the wrinkles you left throughout the source. Unless the +change is minor, there's usually a bit of reorganization to do to make your new +code integrate seamlessly with the rest of the program. If you do it right, the +next person to come along won't be able to tell when any line of code was +written. + +你将一些代码加入了你的游戏,但你不想要下一个人被你留下来的小问题绊倒。除非改变很小,否则就还需要一些工作去微调你的新代码,使之无缝对接到程序的其他部分。如果你做对了,那么下一个看到你代码的人甚至无法说出哪些代码是新加入的。 + +In short, the flow chart for programming is something like: + +简而言之,编程的流程图看起来是这样的: + + + +Get problem → Learn code → Code solution → Clean up → and back around to the beginning. + + + +### How can decoupling help? + +### 解耦怎么帮了忙? + +While it isn't obvious, I think much of software architecture is about that +learning phase. Loading code into neurons is so painfully slow that it pays to +find strategies to reduce the volume of it. This book has an entire section on +[*decoupling* patterns](decoupling-patterns.html), and a large chunk of *Design +Patterns* is about the same idea. + +虽然不是很明显,但我认为很多软件架构都是有关学习阶段。将代码载入到神经元太过于缓慢,找些策略来减少载入的总量是很值得做的事情。这本书有整整一个章节是有关于[*decoupling* patterns](decoupling-patterns.html),还有很多*设计模式*是关于相同的主题。 + +You can define "decoupling" a bunch of ways, but I think if two pieces of code +are coupled, it means you can't understand one without understanding the other. +If you *de*-couple them, you can reason about either side independently. That's +great because if only one of those pieces is relevant to your problem, you just +need to load *it* into your monkey brain and not the other half too. + +你可以用多种方式定义解耦,但我认为如果有两块代码是耦合的,那就意味着你无法只理解其中的一个。如果你*解*耦了他俩,你就可以独自的理解某一块。这当然很好,因为只有一块与你的问题相关,你只需要将*这一块*加载到你的猴脑中而不需要加载另外一块。 + +To me, this is a key goal of software architecture: **minimize the amount of +knowledge you need to have in-cranium before you can make progress.** + +对于我来说,下面这是软件架构的关键目标:*最小化你在做出进展前需要的进入大脑的知识*。 + +The later stages come into play too, of course. Another definition of decoupling +is that a *change* to one piece of code doesn't necessitate a change to another. +We obviously need to change *something*, but the less coupling we have, the less +that change ripples throughout the rest of the game. + +当然,还有些东西会在后期发挥作用。另一种定义解耦的方法是当一块代码有*变化*时,没必要修改另外的代码。我们肯定需要修改*一些东西*,但耦合程度越小,那变化能够波及的范围就越小。 + +## At What Cost? + +## 额外的开销? + +This sounds great, right? Decouple everything and you'll be able to code like +the wind. Each change will mean touching only one or two select methods, and you +can dance across the surface of the codebase leaving nary a shadow. + +听起来很棒,对吧?解耦任何东西,然后代码就像风一样。每一个变化都只修改一两个特定的方法,你在代码库表面跳舞,只留下一两道阴影。 + +This feeling is exactly why people get excited about abstraction, modularity, +design patterns, and software architecture. A well-architected program really is +a joyful experience to work in, and everyone loves being more productive. Good +architecture makes a *huge* difference in productivity. It's hard to overstate +how profound an effect it can have. + +这就是人们对抽象,模块化,设计模式和软件架构兴奋的原因。在一个有好的架构的程序上工作实在是很好的体验,每一个人都希望更有效率的工作。好的架构能造成生产力上*巨大的*不同。很难夸大它那强有力的影响。 + +But, like all things in life, it doesn't come free. Good architecture takes real +effort and discipline. Every time you make a change or implement a feature, you +have to work hard to integrate it gracefully into the rest of the program. You +have to take great care to both organize the code +well and *keep* it organized throughout the thousands of little changes that +make up a development cycle. + +但是,就像生活中的任何事物一样,没有免费的午餐。好的设计需要汗水和纪律。每一次你做出改动或是实现特性,你都需要将它优雅的集成到程序的其他部分。你需要花费大量的努力去组织代码,并继续在开发过程中面对数千次变化仍然保持它的组织。 + + + +You have to think about which parts of the program should be decoupled and +introduce abstractions at those points. Likewise, you have to determine where +extensibility should be engineered in so future changes are easier to make. + +你得想想你的程序的那一部分需要解耦,然后再引入抽象。同样,你需要决定哪些部分要设计得有扩展性来方便未来的变化。 + +People get really excited about this. They envision future developers (or just +their future self) stepping into the codebase and finding it open-ended, +powerful, and just beckoning to be extended. They imagine The One Game Engine To +Rule Them All. + +人们变得很兴奋。他们设想未来的开发者(或者只是他们的未来的自己)步入代码库,并发现它极为开放, +功能强大,只是需要扩展。他们想象一个游戏引擎驾驭着所有需求。 + +But this is where it starts to get tricky. Whenever you add a layer of +abstraction or a place where extensibility is supported, you're *speculating* +that you will need that flexibility later. You're adding code and complexity to +your game that takes time to develop, debug, and maintain. + +但是,这是开始变得棘手的部分。每当你添加了一层抽象或者支持可扩展的地方,你是*赌*你以后需要灵活性。您添加的代码和复杂到你的游戏,这些都需要时间来开发,调试和维护。 + +That effort pays off if you guess right and end up touching that code later. But +predicting the future is *hard*, and when that +modularity doesn't end up being helpful, it quickly becomes actively harmful. +After all, it is more code you have to deal with. + +如果你猜对了,最终接触了代码,那么功夫不负有心人。 但预测未来*很难*,并且如果模块化最终没有帮助,那它就是主动造成伤害。毕竟,你得处理更多的代码。 + + + +When people get overzealous about this, you get a codebase whose architecture +has spiraled out of control. You've got interfaces and abstractions everywhere. +Plug-in systems, abstract base classes, virtual methods galore, and all sorts of +extension points. + +当人们过分关注这点时,你就会得到一个架构失去控制的代码库。接口和抽象无处不在。插件系统,抽象基类,虚方法,还有各种各样的扩展点。 + +It takes you forever to trace through all of that scaffolding to find some real +code that does something. When you need to make a change, sure, there's probably +an interface there to help, but good luck finding it. In theory, all of this +decoupling means you have less code to understand before you can extend it, but +the layers of abstraction themselves end up filling your mental scratch disk. + +回溯所有的脚手架去找真正做事的代码就要消耗无尽的时间。当你需要做出改变,当然,有可能有某个接口能帮上忙,但能不能找到就只能祝你好运了。理论上,解耦意味着在扩展代码之前需要了解的代码更少,但 +抽象层本身就会会填满你的心灵暂存磁盘。 + +Codebases like this are what turn people *against* software architecture, and +design patterns in particular. It's easy to get so wrapped up in the code itself +that you lose sight of the fact that you're trying to ship a *game*. The siren +song of extensibility sucks in countless developers who spend years working on +an "engine" without ever figuring out what it's an engine *for*. + +像这样的代码库让人*反对*软件架构,特别是设计模式。人们很容易沉浸在代码中而忽略你要发布一个*游戏*的这一点。无数的开发者听着“加强可扩展性的警报”而花费多年时间来制作一个“引擎”,却没有搞清楚做引擎是*为了什么*。 + +## Performance and Speed + +## 性能和速度 + +There's another critique of software architecture and abstraction that you hear +sometimes, especially in game development: that it hurts your game's +performance. Many patterns that make your code more flexible rely on virtual +dispatch, interfaces, pointers, messages, and other +mechanisms that all have at least some runtime cost. + +软件架构和抽象有时会被批评,尤其是在游戏开发中: 它伤害了游戏的性能。许多使您的代码更灵活的模式依靠虚拟调度、 接口、 指针、 消息,和其他机制,他们都会消耗一些运行时成本。 + + + +There's a reason for this. A lot of software architecture is about making your +program more flexible. It's about making it take less effort to change it. That +means encoding fewer assumptions in the program. You use interfaces so that your +code works with *any* class that implements it instead of just the one that does +today. You use observers and messaging to let two parts of the +game talk to each other so that tomorrow, it can easily be three or four. + +还有一个原因。很多软件架构的目的是使你的程序更加灵活。这让改变它需要较少的努力。编码时对程序有更少的假设。您可以使用接口,让您的代码可与*任何*实现它的类交互,而不仅仅是*现在*写的类。今天。您可以使用观察者消息让游戏的两部分交流,而以后它可以很容易地扩展为三个或四个部分相互交流。 + +But performance is all about assumptions. The practice of optimization thrives +on concrete limitations. Can we safely assume we'll never have more than 256 +enemies? Great, we can pack an ID into a single byte. Will we only call a method +on one concrete type here? Good, we can statically dispatch or inline it. Are +all of the entities going to be the same class? Great, we can make a nice contiguous array of them. + +但性能与假设相关。实践优化基于确定的限制。我们永远不会有超过256种敌人吗?好,我们就可以将ID编码为为一个字节。请问我们在一个具体类型中只调用一个方法吗?好,我们可以做静态调度或内联的。所有的实体都是同一类?太好了,我们可以做一个 连续数组存储他们。 + +This doesn't mean flexibility is bad, though! It lets us change our game +quickly, and *development* speed is absolutely vital for getting to a fun +experience. No one, not even Will Wright, can come up with a balanced game +design on paper. It demands iteration and experimentation. + +但这并不意味着灵活性是坏的!它可以让我们快速改进我们的游戏,*开发*速度是获取有趣开发经验的绝对重要因素。没有任何人,甚至 Will Wright,可以在纸面上构建一个平衡的游戏。这需要迭代和实验。 + +The faster you can try out ideas and see how they feel, the more you can try and +the more likely you are to find something great. Even after you've found the +right mechanics, you need plenty of time for tuning. A tiny imbalance can wreck +the fun of a game. + +越快尝试想法然后看看效果如何,你就越能尝试更多的东西,就越有可能找到有价值的东西。就算发现了正确的机制之后,你也需要充足的时间做调整。一个微小的不平衡可能破坏整个游戏的乐趣。 + +There's no easy answer here. Making your program more flexible so you can +prototype faster will have some performance cost. Likewise, optimizing your code +will make it less flexible. + +这里没有简单朴实的回答。让你的程序更加灵活,你就在损失一点点性能的前提下更快的做出原型。同样的优化你的代码会让它变得不那么灵活。 + +My experience, though, is that it's easier to make a fun game fast than it is to +make a fast game fun. One compromise is to keep the code flexible until the +design settles down and then tear out some of the abstraction later to improve +your performance. + +就我个人经验而言,让一个有趣的游戏变快比让一个快速的游戏变有趣简单的多。一种折中的办法是保持代码灵活直到设计定下来,在抽出抽象层来提高性能。 + +## The Good in Bad Code + +## 糟糕代码的优势 + +That brings me to the next point which is that there's a time and place for +different styles of coding. Much of this book is about making maintainable, +clean code, so my allegiance is pretty clearly to doing things the "right" way, +but there's value in slapdash code too. + +这就来到了我们的下一个观点:不同的代码风格各有千秋。这本书的大部分是关于保持干净可控的代码,所以我坚持应该用*正确*地方式写代码,但糟糕的代码也有一定能够的优势。 + +Writing well-architected code takes careful thought, and that translates to +time. Moreso, *maintaining* a good architecture over the life of a project takes +a lot of effort. You have to treat your codebase like a good camper does their +campsite: always try to leave it a little better than you found it. + +编写良好架构的代码需要仔细的思考,这就会转化为时间的代价。在项目的整个周期中*保持*良好的架构需要花费大量的努力。你需要像一名露营者处理营地一样小心处理你的代码库:总是保持其优于你接触它的时候。 + +This is good when you're going to be living in and working on that code for a +long time. But, like I mentioned earlier, game design requires a lot of +experimentation and exploration. Especially early on, it's common to write code +that you *know* you'll throw away. + +当你要在一个项目上花费很久时间的话,这是很好的。但是,就像我早先时候提到的,游戏设计需要很多实验和探索。特别是在早期,写一些需要扔掉的代码是很普遍的事情。 + +If you just want to find out if some gameplay idea plays right at all, +architecting it beautifully means burning more time before you actually get it +on screen and get some feedback. If it ends up not working, that time spent +making the code elegant goes to waste when you delete it. + +如果你只想试试游戏的某些主意是不是正确的,良好的设计意味着你在屏幕上看到和获取反馈之前要消耗很长的时间。如果最后证明这个点子不对,那么当你删除代码的时候,那些你花在让代码更加优雅的时间就完全的浪费了。 + +Prototyping -- slapping together code that's just barely functional enough to +answer a design question -- is a perfectly legitimate programming practice. +There is a very large caveat, though. If you write throwaway code, you *must* +ensure you're able to throw it away. I've seen bad managers play this game time +and time again: + +原型——一坨勉强拼凑在一起,只能回答设计问题简单代码——是一个完全合理的编程习惯。虽然当你写一次性代码的时候,*必须*保证你可以抛弃它。我见过很多次糟糕的经理人在玩以下这种把戏: + +> Boss: "Hey, we've got this idea that we want to try out. Just a prototype, so +> don't feel you need to do it right. How quickly can you slap something +> together?" + +> Dev: "Well, if I cut lots of corners, don't test it, don't document it, and it +> has tons of bugs, I can give you some temp code in a few days." + +> Boss: "Great!" + +*A few days pass...* + +> Boss: "Hey, that prototype is great. Can you just spend a few hours cleaning +> it up a bit now and we'll call it the real thing?" + +> 老板:“嗨,我们有些想试试的点子。只要原型,不需要做的很好。你能多快搞定?” + +> 开发者:“额,如果我删掉这些部分,不测试,不写文档,允许很多的漏洞,那么几天后我能给你一个临时的代码文件。” + +> 老板:“太好了。” + +*几天后* + +> 老板:“嘿,原型很棒,你能花上几个小时清理一下然后变成成品吗?” + +You need to make sure the people using the throwaway code understand that even though it kind of +looks like it works, it *cannot* be maintained and *must* be rewritten. If +there's a *chance* you'll end up having to keep it around, you may have to +defensively write it well. + +你得让人们清楚,可抛弃的代码即使看上去能够工作,也不能被*维护*,*必须*重写。如果*有可能*你要维护这段代码,你就得防御性好好编写它。 + + + +## Striking a Balance + +## 保持平衡 + +We have a few forces in play: + +1. We want nice architecture so the code is easier to + understand over the lifetime of the project. +2. We want fast runtime performance. +3. We want to get today's features done quickly. + +有些因素在相互角力: + +1. 为了在项目的整个生命周期保持其可读性,我们需要好的架构。 +2. 我们需要更快的运行时表现。 +3. 我们需要让现在的特性更快的实现。 + + + +These goals are at least partially in opposition. Good architecture improves +productivity over the long term, but maintaining it means every change requires +a little more effort to keep things clean. + +这些目标至少是部分对立的。好的设计长期来看提高了生产力,但是维护之意味着每个变化都需要做出更多的努力让代码保持清洁。 + +The implementation that's quickest to write is rarely the quickest to *run*. +Instead, optimization takes significant engineering time. Once it's done, it +tends to calcify the codebase: highly optimized code is inflexible and very +difficult to change. + +草就的代码很少是*运行时*最快的代码。相反的是,提升性能需要很多的编程时间。一旦完成了,它会污染代码库:高度优化的代码不灵活,很难进行改动。 + +There's always pressure to get today's work done today and worry about +everything else tomorrow. But if we cram in features as quickly as we can, our +codebase will become a mess of hacks, bugs, and inconsistencies that saps our +future productivity. + +总有今日事今日毕的压力。但是我们如果尽可能快的实现我们的特性,我们的代码库就会充满黑魔法,漏洞和混乱,阻碍我们未来的生产力。 + +There's no simple answer here, just trade-offs. From the email I get, this +disheartens a lot of people. Especially for novices who just want to make a +game, it's intimidating to hear, "There is no right answer, just different +flavors of wrong." + +这里没有简单的解决方案,只有权衡。从我收到的邮件看,这伤了很多人的心。特别是那些仅仅是想做个游戏的人,它似乎是在恐吓,“没有正确的答案,只有不同的口味。” + +But, to me, this is exciting! Look at any field that people dedicate careers to +mastering, and in the center you will always find a set of intertwined +constraints. After all, if there was an easy answer, everyone would just do +that. A field you can master in a week is ultimately boring. You don't hear of +someone's distinguished career in ditch digging. + +对我来说,这让人兴奋!看看人们从事的任何领域,你总能在其中发现某些相互抵触的限制。无论如何,如果有简单的答案,每个人都会执行之。一周就能掌握的领域是很无聊的。你从来没有听说过有人讨论挖坑的事业。 + + + +To me, this has much in common with games themselves. A game like chess +can never be mastered because all of the pieces are so perfectly balanced +against one another. This means you can spend your life exploring the vast space +of viable strategies. A poorly designed game collapses to the one winning tactic +played over and over until you get bored and quit. + +对我来说,这和游戏有很多相似之处。国际象棋之类的游戏永远不能被掌握,因为每一个棋子都很完美的与其他棋子相平衡。这就意味着你可以花费一生的时间探索广阔的可选策略。糟糕的游戏就像井字棋,你玩了一遍又一遍,直到你厌烦了就退出。 + +## Simplicity + +## 简单 + +Lately, I feel like if there is any method that eases these constraints, it's +*simplicity*. In my code today, I try very hard to write the cleanest, most +direct solution to the problem. The kind of code where after you read it, you +understand exactly what it does and can't imagine any other possible solution. + +最近,我感觉如果有什么能缓解这些限制,那就是*简单*。在我现在的代码中,我努力去写最简单,最直接的解决方案。你读过后这种代码后,完全理解了它在做什么,想不出其他完成的方法。 + +I aim to get the data structures and algorithms right (in about that order) and +then go from there. I find if I can keep things simple, there's less code +overall. That means less code to load into my head in order to change it. + +我的目的是正确获得数据结构和算法(大致在这样的顺序下)然后在从那里出发。我发现如果我能让事物变得简单,这里就会有更少的代码。这就意味着要改动时有更少的代码加入我的脑海。 + +It often runs fast because there's simply not as much overhead and not much code +to execute. (This certainly isn't always the case though. You can pack a lot of +looping and recursion in a tiny amount of code.) + +它通常跑的很快,因为这里没什么开销,也没什么代码执行。(虽然大部分时候事实并非如此。你可以在一小段代码里加入大量的循环和递归)。 + +However, note that I'm not saying simple code takes +less time to *write*. You'd think it would since you end up with less total +code, but a good solution isn't an accretion of code, it's a *distillation* of +it. + +但是,注意我并没有说简单的代码需要更少的时间*编写*。你会这么觉得是因为你最终获得了更少的代码,但是好的解决方案不是往代码中注水,而是*蒸干*代码。 + + + +We're rarely presented with an elegant problem. Instead, it's a pile of use +cases. You want the X to do Y when Z, but W when A, and so on. In other words, a +long list of different example behaviors. + +我们很少遇到优雅表达的问题。实际上,是一堆用况。你想要X在Z情况下做Y,在A情况下作W,诸如此类的。换言之,一长列不同表现的例子。 + +The solution that takes the least mental effort is to just code up those use +cases one at a time. If you look at novice programmers, that's what they often +do: they churn out reams of conditional logic for each case that popped into +their head. + +最不消耗心血的解决方法就是编写用况编写一段代码。如果你看看新手程序员,他们经常这么干:他们为每一个落到他手上的问题编写逻辑循环。 + +But there's nothing elegant in that, and code in that style tends to fall over +when presented with input even slightly different than the examples the coder +considered. When we think of elegant solutions, what we often have in mind is a +*general* one: a small bit of logic that still correctly covers a large space of +use cases. + +但这一点也不优雅,那种风格的代码遇到一点点程序员没想到的输入就会崩溃。当我们想象优雅的代码时,我们想的是*通用*的那一个:只需要很少的逻辑部分就可以覆盖整个用况。 + +Finding that is a bit like pattern matching or solving a puzzle. It takes effort +to see through the scattering of example use cases to find the hidden order +underlying them all. It's a great feeling when you pull it off. + +找到这样的方法有点像模式识别或者解决谜题。他需要努力去识别散乱的例子下隐藏的规律。当你完成的时候感觉好得不能再好。 + +## Get On With It, Already + +## 就快完了 + +Almost everyone skips the introductory chapters, so I congratulate you on making +it this far. I don't have much in return for your patience, but I'll offer up a +few bits of advice that I hope may be useful to you: + +几乎每个人都会跳过介绍章节,所以祝贺你看到这里。我没有太多的东西回报你的耐心,但我还是有一些建议给你,希望对你有用: + + * Abstraction and decoupling make evolving your program faster and easier, but + don't waste time doing them unless you're confident the code in question needs + that flexibility. + + * Think about and design for performance throughout + your development cycle, but put off the low-level, nitty-gritty optimizations + that lock assumptions into your code until as late as possible. + + * 抽象和耦合让不断扩展代码更快更容易,但除非你知道你需要这样的灵活性,否则不要做。 + + * 在你的整个开发周期中考虑并为性能设计,但是尽可能推迟那些底层的,基本事实的优化,那会锁死你的代码。 + + + + + * Move quickly to explore your game's design space, but don't go so fast that + you leave a mess behind you. You'll have to live with it, after all. + + * If you are going to ditch code, don't waste time making it pretty. Rock + stars trash hotel rooms because they know they're going to check out the + next day. + + * But, most of all, **if you want to make something fun, have fun making + it.** + + * 快速的探索你游戏的设计空间,但不要跑的太快,在你身后留下一团乱麻。毕竟,你总得回来处理他们。 + + * 如果你打算抛弃这段代码,就不要尝试将其写的完美。摇滚明星将旅店房间弄得一团糟,因为他们知道明天会有人来打扫干净。 + + * 但最重要的是,**如果你想要做出让人享受的东西,那就享受做它的过程。** \ No newline at end of file diff --git a/book/behavioral-patterns.markdown b/book/behavioral-patterns.markdown new file mode 100644 index 0000000..e77a4f6 --- /dev/null +++ b/book/behavioral-patterns.markdown @@ -0,0 +1,33 @@ +^title Behavioral Patterns + +行为模式 + +Once you've built your game's set and festooned it with actors and props, all +that remains is to start the scene. For this, you need behavior -- the +screenplay that tells each entity in your game what to do. + +一旦你做好你的游戏设定,挂满了角色和道具,剩下的就是启动场景。对于这点,你需要行为——告诉每一个你游戏中的实体做什么的剧本。 + +Of course all code is "behavior", and all software is defining behavior, but +what's different about games is often the *breadth* of it that you have to +implement. While your word processor may have a long list of features, it pales +in comparison with the number of inhabitants, items, and quests in your average +role-playing game. + +当然,所有的代码都是“行为”,并且所有的软件都是定义行为,但在游戏中有所不同的是,行为通常很*多*。你的文字处理器也许有很长的特性清单,但是与通常玩的角色扮演游戏中的居民,物品和任务数量相比,那就相形见绌了。 + +The patterns in this chapter help to quickly define and refine a large quantity of +maintainable behavior. [Type Objects](type-object.html) create +categories of behavior without the rigidity of defining an actual class. A +[Subclass Sandbox](subclass-sandbox.html) gives you a safe set of primitives +you can use to define a variety of behaviors. The most advanced option is +[Bytecode](bytecode.html), which moves behavior out of code entirely and into +data. + +本章的模式帮助快速定义和完善大量的维护行为。[类型对象]定义行为的类别而不需要完成一个真正的类。[子类沙盒]给你定义各种行为的安全原语。最先进的方法是[字节码],将行为从代码中拖出,放入数据。 + +## The Patterns + +* [Bytecode](bytecode.html) +* [Subclass Sandbox](subclass-sandbox.html) +* [Type Object](type-object.html) diff --git a/book/bytecode.markdown b/book/bytecode.markdown new file mode 100644 index 0000000..1184a66 --- /dev/null +++ b/book/bytecode.markdown @@ -0,0 +1,1465 @@ +^title Bytecode +^section Behavioral Patterns + +## Intent + +## 意图 + +*Give behavior the flexibility of data by encoding it as instructions for a +virtual machine.* + +*将行为编码为虚拟机器上的指令来赋予其数据的灵活性* + +## Motivation + +## 动机 + +Making games may be fun, but it certainly ain't easy. Modern games require enormous, complex codebases. Console manufacturers and +app marketplace gatekeepers have stringent quality requirements, and a single +crash bug can prevent your game from shipping. + +制作游戏也许很有趣,但绝不容易。现代的游戏需要庞杂的代码库。游戏机制造商和应用程序市场看门人有着严格的质量要求,一个小小的崩溃漏洞就能阻止你的游戏发售。 + + + +At the same time, we're expected to squeeze every drop of performance out of the +platform. Games push hardware like nothing else, and we have to optimize +relentlessly just to keep pace with the competition. + +与此同时,我们希望榨干平台的最后一点性能。游戏推动硬件发展首屈一指,我们坚持不懈的优化只是为了跟上竞争。 + +To handle these high stability and performance requirements, we reach for +heavyweight languages like C++ that have both low-level expressiveness to make +the most of the hardware and rich type systems to prevent or at least corral +bugs. + +为了控制稳定和性能需求,我们使用重量级的编程语言比如C++,来同时保证容纳大多数硬件的底层和防止漏洞的丰富类型系统。 + +We pride ourselves on our skill at this, but it has its cost. Being a proficient +programmer takes years of dedicated training, after which you must contend with +the sheer scale of your codebase. Build times for large games can vary somewhere +between "go get a coffee" and "go roast your own beans, hand-grind them, pull an +espresso, foam some milk, and practice your latte art in the froth". + +我们对自己的手艺感到自豪,但它有其代价。做一个精通的程序员需要多年的训练,之后你要对抗代码库的规模增长。构建大型游戏的时间可能在“喝杯咖啡”和“烤自己的咖啡店,手磨它们,弄杯espresso,把牛奶打到发泡,在你的拿铁咖啡做一些艺术图案。” + +On top of these challenges, games have one more nasty constraint: *fun*. Players +demand a play experience that's both novel and yet carefully balanced. That +requires constant iteration, but if every tweak requires bugging an engineer to +muck around in piles of low-level code and then waiting for a glacial recompile, +you've killed your creative flow. + +在这些挑战之上,游戏多了一个讨厌的限制:“乐趣”。玩家需要仔细权衡过的新奇体验。那需要不断的重复,但是如果每一个调整都需要让工程师调整底层的代码然后等待漫长的编译过程,你就毁掉了你的创意流程。 + +### Spell fight! + +### 法术战斗! + +Let's say we're working on a magic-based fighting game. A pair of wizards square +off and fling enchantments at each other until a victor is pronounced. We could +define these spells in code, but that means an engineer has to be involved every +time one is modified. When a designer wants to tweak a few numbers and get a +feel for them, they have to recompile the entire game, reboot it, and get back +into a fight. + +假设我们在完成一个基于魔法的格斗游戏。一对巫师在广场上飞行互相丢法术,直到胜负已分。我们可以将这些法术都定义在代码中,但这就意味着每当法术修改,工程师就被牵扯进来了。当设计者想要修改一两个数字感觉一下效果,就要重新编译整个工程,重启它,然后重新回到战斗。 + +Like most games these days, we also need to be able to update the game after it ships, +both to fix bugs and to add new content. If all of these spells are hard-coded, +then updating them means patching the actual game executable. + +像现在的许多游戏一样,我们需要在发售之后更新游戏,修复漏洞或是添加新的内容。如果所有的的法术都是硬编码的,那么修改他们意味着向游戏的运行文件上打补丁。 + +Let's take things a bit further and say that we also want to support *modding*. +We want *users* to be able to create their own spells. If those are in code, +that means every modder needs a full compiler toolchain to build the game, and +we have to release the sources. Worse, if they have a bug in their spell, it can +crash the game on some other player's machine. + +再扯得远一点,我们还想支持*MOD*。我们想让*玩家*能创造他们自己的法术。如果这些是在代码中,那么意味着每个MODDER都得拥有编译游戏的整套工具链,而我们得发布那些资源,如果他们在他们的法术上有个漏洞,那么会把其他人的游戏也搞崩溃。 + +### Data > code + +### 数据和代码 + +It's pretty clear that our engine's implementation language isn't the right fit. +We need spells to be safely sandboxed from the core game. We want them to be +easy to modify, easy to reload, and physically separate from the rest of the +executable. + +很明显我们实现引擎的编程语言不是好的选择。我们需要法术放在与核心游戏隔绝的沙箱中。我们想要他们容易的修改,容易的加载,与其他可执行部分隔绝开来。 + +I don't know about you, but to me that sounds a lot like *data*. If we can +define our behavior in separate data files that the game engine loads and +"executes" in some way, we can achieve all of our goals. + +我不知道你是怎么想到,但我听上去这有点像是*数据*。如果我们能够在分立的数据文件中定义我们的行为,而游戏引擎还能加载并“执行”它们,我们就可以实现所有的目标。 + +We just need to figure out what "execute" means for data. How do you make some +bytes in a file express behavior? There are a few ways to do this. I think it +will help you get a picture of *this* pattern's strengths and weaknesses if we +compare it to another one: the Interpreter pattern. + +我们需要指出“执行”对于数据的意义。你如何让文件中的数据指示为行为呢?这里有几种方式。我认为给你一副与编译模式 对比的强弱图会好理解些。 + +### The Interpreter pattern + +### 编译模式 + +I could write a whole chapter on this pattern, but four other guys already +covered that for me. Instead, I'll cram the briefest of introductions in here. +It starts with a language -- think *programming* language -- that you want to +execute. Say, for example, it supports arithmetic expressions like this: + +关于这个模式我就能写一章,但是有四个家伙的工作早就涵盖了我的,相反,我会给一些补习班的简短介绍在这里。他从一种语言开始——想想*编程语言*——从你想要执行的语言开始。比如,他会支持这样的算术表达式 + + (1 + 2) * (3 - 4) + +Then, you take each piece of that expression, each rule in the language's +grammar, and turn it into an *object*. The number literals will be objects: + +然后,你把每一块的表达式,每一条语言中的规则,都装到一个*对象*中去。数字都变成对象: + +A series of number literal objects. + +Basically, they're little wrappers around the raw value. The operators will be +objects too, and they'll have references to their operands. If you take into +account the parentheses and precedence, that expression magically turns into a little tree of objects like so: + +基本上,他们是原始值的小包装。操作符也是对象,他们拥有他们操作的值的引用。如果你考虑了括号和优先级,那么表达式就会变成这样的小树: + +A syntax tree. The number literals are connected by operator objects. + + + +The Interpreter pattern isn't about *creating* that tree; it's about *executing* +it. The way it works is pretty clever. Each object in the tree is an expression +or a subexpression. In true object-oriented fashion, we'll let expressions +evaluate themselves. + +解释器模式与*创建*这棵树无关,他是关于*执行*这棵树。它工作的方式非常聪明。树中的每个对象是表达 +或子表达式。用真正的面向对象的方式描述,我们会让表达式自己执行自己。 + +First, we define a base interface that all expressions implement: + +首先,我们定义所有表达式都实现了的基本接口: + +^code expression + +Then, we define a class that implements this interface for each kind of expression in our +language's grammar. The simplest one is numbers: + +然后,我们定义一个类,为我们语法中的每种表达式实现这个接口。最简单的是数字: + +^code number + +A literal number expression simply evaluates to its value. Addition and +multiplication are a bit more complex because they contain subexpressions. +Before they can evaluate themselves, they need to recursively evaluate their +subexpressions. Like so: + +一个数字表达式简单的等于它的值。加法和乘法是有点复杂,因为它们包含子表达式。在递归地计算其子表达式之后,才能计算自己的值。像这样: + + + +^code addition + + + +Pretty neat right? Just a couple of simple classes and now we can represent and +evaluate arbitrarily complex arithmetic expressions. We just need to create the +right objects and wire them up correctly. + +整齐漂亮吧?只是一对简单的类,现在我们可以表示和计算任意复杂的算术表达式。我们只需要创建正确的对象,并正确连起来。 + + + +It's a beautiful, simple pattern, but it has some +problems. Look up at the illustration. What do you see? Lots of little boxes, +and lots of arrows between them. Code is represented as a sprawling fractal tree +of tiny objects. That has some unpleasant consequences: + +这是一个优美简单的模式,但它有自己的问题。看看插图,你看到了什么?大量的小盒子,以及在他们之间大量的箭头。代码别表示为小物体组成的巨大分形树。这有一些令人不快的后果: + + * Loading it from disk requires instantiating and wiring up tons of these + small objects. + + * 从磁盘上加载它需要实例化并连接大量这种小对象。 + + * Those objects and the pointers between them use a lot of memory. On a 32-bit machine, that little arithmetic + expression up there takes up at least 68 bytes, not including padding. + + * 这些对象和他们之间的指针会占据大量的内存。在32位机器上,那个小的算术表达式至少占据68字节,还不包括填充的部分。 + + + + * Traversing the pointers into subexpressions is murder on your data cache. Meanwhile, all of those virtual method calls + wreak carnage on your instruction cache. + + * 遍历到子表达式的指针是对你数据缓存的谋杀。同时所有的虚函数调用是对你指令缓存的屠杀。 + + + +Put those together, and what do they spell? S-L-O-W. There's a reason most +programming languages in wide use aren't based on the Interpreter pattern. It's +just too slow, and it uses up too much memory. + +将这些拼到一起,你怎么念?M-A-N。这就是为什么大多数广泛应用的编程语言不基于解释器模式。这太慢了,也太消耗内存了。 + +### Machine code, virtually + +### 机器码,虚拟的 + +Consider our game. When we run it, the player's computer doesn't traverse a +bunch of C++ grammar tree structures at runtime. Instead, we compile it ahead of +time to machine code, and the CPU runs that. What's machine code got going for +it? + +想想我们的游戏。当我们运行时,玩家的电脑在运行时不会遍历一堆C++语法结构树。相反地,我们踢球为其编译成了机器码,CPU基于机器码运行。机器码有什么好处呢? + + * *It's dense.* It's a solid, contiguous blob of binary data, and no bit goes + to waste. + + * *它是密集的。*它是一块坚实连续的二进制数据块,没有一位被浪费了。 + + * *It's linear.* Instructions are packed together and executed one right after + another. No jumping around in memory (unless you're doing actual control + flow, of course). + + * *它是线性的。*指令被打成一包,一条接一条的执行。没有在内存里到处乱跳(除非你控制代码流这么干) + + * *It's low-level.* Each instruction does one relatively minimal thing, and + interesting behavior comes from *composing* them. + + * *它是底层的。*每一条指令都做一件很小的事,有趣的行为从他们的*组织*中诞生。 + + * *It's fast.* As a consequence of all of these (well, and the fact that it's + implemented directly in hardware), machine code runs like the wind. + + * *它速度快。*在所有以上的要素作用下(当然,还有它是直接在硬件上实现的),机器码跑的跟风一样快。 + +This sounds swell, but we don't want actual machine code for our spells. Letting +users provide machine code which our game executes is just begging for security problems. What we need is a compromise between the +performance of machine code and the safety of the Interpreter pattern. + +这听起来很好,但我们不希望为我们的番薯提供真正的机器码。让玩家提供游戏运行时的机器码简直就是在乞求安全问题。我们需要的是机器代码性能和解释器模式的安全性之间的一种妥协方案。 + +What if instead of loading actual machine code and executing it directly, we +defined our own *virtual* machine code? We'd then write a little emulator for it +in our game. It would be similar to machine code -- dense, linear, relatively +low-level -- but would also be handled entirely by our game so we could safely +sandbox it. + +如果我们不是加载机器码并直接执行它,而是定义我们自己的*虚拟*机器码?然后,我们会在我们的游戏写一个小模拟器。这将与机器码类似——密集,线性,相对底层——但也由游戏直接掌控,所以我们可以放心地将其放入沙箱。 + + + +We'd call our little emulator a *virtual machine* +(or "VM" for short), and the synthetic binary machine code it runs *bytecode*. +It's got the flexibility and ease of use of defining things in data, but it has better +performance than higher-level representations like the Interpreter pattern. + +我们将小模拟器称为*虚拟机*(或简称“VM”),它运行*字节码*合成二进制机器码。它有数据的灵活性和易用性,但它比高层的解释器模式有更好的性能。 + + + +This sounds daunting, though. My goal for the rest of this chapter is to show +you that if you keep your feature list pared down, it's actually pretty +approachable. Even if you end up not using this pattern yourself, you'll at +least have a better understanding of Lua and many other languages which are +implemented using it. + +这听起来有点吓人。这章的其余部分目的是展示,如果把功能列表缩减下来,它实际上是相当通俗易懂。即使你最终没有使用这个模式,你至少对Lua和许多其他语言有一个更好的了解。 + +## The Pattern + +## 模式 + +An **instruction set** defines the low-level operations that can be performed. +A series of instructions is encoded as a **sequence of bytes**. A **virtual machine** executes +these instructions one at a time, using a **stack for intermediate values**. By +combining instructions, complex high-level behavior can be defined. + +一个**指令集**定义了可以执行的低层操作。一系列的指令被编码为**字节序列**。**虚拟机**使用**堆栈中间值**一个一个执行这些指令。通过结合指令,复杂的高层行为可以被定义。 + +## When to Use It + +## 何时使用 + +This is the most complex pattern in this book, and it's not something to throw into +your game lightly. Use it when you have a lot of behavior you need to define and +your game's implementation language isn't a good fit because: + +这是本书中最复杂的模式,它不能轻易的加入游戏中。使用之当需要定义很多行为,而游戏的实现语言因为以下原因不能很好的完成任务时: + + * It's too low-level, making it tedious or error-prone to program in. + + * 它太过于底层,容易制造乏味或者充满错误的程序。 + + * Iterating on it takes too long due to slow compile times or other tooling + issues. + + * 由于缓慢的编译时间或者其他工具的文件需要很久才能迭代。 + + * It has too much trust. If you want to ensure the behavior being defined + can't break the game, you need to sandbox it from the rest of the codebase. + + * 它肩负了太多信任。如果你想保证行为不会破坏游戏,你需要将其与代码的其他部分隔开。 + +Of course, that list describes a bunch of your game. Who doesn't want a faster +iteration loop or more safety? However, that doesn't come for free. Bytecode is +slower than native code, so it isn't a good fit for performance-critical parts +of your engine. + +当然,该列表描述了一堆你的游戏。谁不希望有一个更快的迭代循环和更多的安全?然而,世上没有免费的午餐。字节码比本地代码慢,所以它不适合引擎的性能关键部分。 + +## Keep in Mind + +## 记住 + +There's something seductive about creating your +own language or system-within-a-system. I'll be doing a minimal example here, +but in the real world, these things tend to grow like vines. + +创建自己的语言或者建立系统中的系统是很有趣的。我在这里做一个小小的演示,但在真实世界,这些东西会像藤蔓一样蔓延。 + + + +Every time I see someone define a little language or a scripting system, they +say, "Don't worry, it will be tiny." Then, inevitably, they add more and more +little features until it's a full-fledged language. +Except, unlike some other languages, it grew in an ad-hoc, organic fashion and +has all of the architectural elegance of a shanty town. + +每当我看到有人定义一个小语言或脚本系统,他们都说,“别担心,它很小。”于是,不可避免地,他们增加更多 +小功能,直到完成了一个完整的语言。除了,和其它语言不同,它长成了特设的,绿色的,并拥有棚户区建筑般的优雅。 + + + +Of course, there's nothing *wrong* with making a full-fledged language. Just +make sure you do so deliberately. Otherwise, be very careful to control the +scope of what your bytecode can express. Put a short leash on it before it runs +away from you. + +当然,做一个完整的语言并没有什么错。只是确保你是故意这么做的。否则,小心的控制字节码可以表达的含义。在其失控前为其系上皮带。 + +### You'll need a front-end + +### 你需要一个前端 + +Low-level bytecode instructions are great for performance, but a binary bytecode +format is *not* what your users are going to author. One reason we're moving +behavior out of code is so that we can express it at a *higher* level. If C++ is +too low-level, making your users effectively write in assembly language -- even one of your own design -- isn't +an improvement! + +底层的字节码指令有利于性能,但是二进制的字节码格式*不是*你用户能写的。一个我们将行为移出代码的原因是我们想要在*高层*表现它。如果C++太过底层,那么让你的用户有效的写汇编——虽然是你的设计——不是一个改进方案! + + + +Much like the Gang of Four's Interpreter pattern, it's assumed that you also +have some way to *generate* the bytecode. Usually, users author their behavior +in some higher-level format, and a tool translates that to the bytecode that our +virtual machine understands. In other words, a compiler. + +就像GoF的解释器模式,它假设有一些方法来*生成*字节码。通常情况下,用户通过顶层格式编写行为,再用工具将其翻译为虚拟机理解的字节码。换言之,一个解释器。 + +I know, that sounds scary. That's why I'm mentioning it here. If you don't have +the resources to build an authoring tool, then bytecode isn't for you. But as +we'll see later, it may not be as bad as you think. + +我知道,这听起来很吓人。这就是为什么我在这里提到它。如果你没有资源打造一个创作工具,那么字节码不适合你。但是,以后我们会看到,它可能不像你想象的那样糟。 + +### You'll miss your debugger + +### 你会想念你的调试器 + +Programming is hard. We know what we want the machine to do, but we don't always +communicate that correctly -- we write bugs. To help find and fix those, we've +amassed a pile of tools to understand what our code is doing wrong, and how to +right it. We have debuggers, static analyzers, decompilers, etc. All of those tools are +designed to work with some existing language: either machine code or something +higher level. + +编程是很难的。我们知道我们想要机器做什么,但我们并不总能正确的传达——我们写出了漏洞。为了帮助查找和解决这些,我们已经积累了一堆工具来了解我们的代码做错什么,以及如何修正。我们有调试器,静态分析器,反编译工具等。所有这些工具都是为现有的语言设计的:无论是机器码还是某些更高层次的东西。 + +When you define your own bytecode VM, you leave those tools behind. Sure, you +can step through the VM in your debugger, but that tells you what the VM +*itself* is doing, and not what the bytecode it's interpreting is up to. It +certainly doesn't help you map that bytecode back to the high-level form it was +compiled from. + +当你定义自己的虚拟机字节码,你离开了这些工具。当然,你可以通过调试器进入虚拟机,但它告诉你虚拟机 +*本身*在做什么,而不是字节码被翻译成了什么。它不能把字节码映射回原先的高层次的形式。 + +If the behavior you're defining is simple, you can scrape by without too much +tooling to help you debug it. But as the scale of your content grows, plan to +invest real time into features that help users see what their bytecode is doing. +Those features might not ship in your game, but +they'll be critical to ensure that you actually *can* ship your game. + +如果你定义的行为很简单,你可能无需太多工具帮助调试就能勉强坚持下来。但随着内容的规模增长,计划 +投入些时间完成一些功能,让用户看到他们的字节码在做什么。这些功能也许不随着你的游戏发售,但 +他们至关重要,他们能确保你确实*能*发售您的游戏。 + + + +## Sample Code + +## 示例代码 + +After the previous couple of sections, you might be surprised how +straightforward the implementation is. First, we need to craft an instruction +set for our VM. Before we start thinking about bytecode and stuff, let's just +think about it like an API. + +经历了前面几个章节后,你也许会为实现有多么直接而感到惊讶。首先我们需要为我们的VM设定一套指令集。在我们开始考虑字节码之类的东西前,我们先像思考API一样的思考它。 + +### A magical API + +### 法术的API + +If we were defining spells in straight C++ code, what kind of API would we need +for that code to call into? What are the basic operations in the game engine +that spells are defined in terms of? + +如果你直接使用C++代码定义法术,代码需要定义何种API?在游戏引擎中定义的法术的基本行为是什么样的? + +Most spells ultimately change one of the stats of a wizard, so we'll start with +a couple for that: + +大多数法术最终改变一个巫师的状态,因此我们先从一对这样的代码开始。 + +^code magic-api + +The first parameter identifies which wizard is affected, say `0` for the +player's and `1` for their opponent. This way, healing spells can affect the +player's own wizard, while damaging attacks harm their nemesis. These three +little methods cover a surprisingly wide variety of magical effects. + +第一个个参数指定哪个巫师被影响力,`0`代表玩家而`1`代表对手。以这种方式,治愈法术可以影响玩家自己的巫师,而伤害法术攻击他的敌人。这三个小方法覆盖了出人意料多的法术影响。 + +If the spells just silently tweaked stats, the game logic would be fine, but +playing it would bore players to tears. Let's fix that: + +如果法术只是默默地调整统计数据,游戏逻辑就已经完成了,但玩这样的游戏会让玩家流泪。让我们修复之: + +^code magic-api-fx + +These don't affect gameplay, but they crank up the intensity of the gameplay +*experience*. We could add more for camera shake, animation, etc., but this is +enough to get us started. + +这并不影响游戏,但它们增强了游戏的*体验*。我们可以增加一些镜头晃动,动画之类的,但这已经足以作为开始的部分了。 + +### A magical instruction set + +### 法术的指令集 + +Now let's see how we'd turn this *programmatic* API into something that can be +controlled from data. Let's start small and then we'll work our way up to the +whole shebang. For now, we'll ditch all of the parameters to these methods. +We'll say the `set___()` methods always affect the player's own wizard and +always max out the stat. Likewise, the FX operations always play a single +hard-coded sound and particle effect. + +现在让我们把这种*程序化*的API转化为可以被数据控制的东西。我们从小处开始,然后我们慢慢拓展到整个部分。现在,我们要抛弃方法的所有参数。我们假设`set__()`方法影响玩家自己的巫师,总是直接将状态最大化。同样,FX操作一直播放硬编码的声音和粒子效果。 + +Given that, a spell is just a series of instructions. Each one identifies which +operation you want to perform. We can enumerate them: + +这样,一个法术就只是一系列指令。每一条都代表了你想要呈现的操作。我们可以列举出来: + +^code instruction-enum + +To encode a spell in data, we store an array of `enum` values. We've only got +a few different primitives, so the range of `enum` values easily fits into a byte. +This means the code for a spell is just a list of bytes -- ergo +"bytecode". + +将法术编码进数据,我们存储了一数组`enum`值。我们几个不同的原语,因此`enum`值的范围可以存储到一个字节中。这就意味着法术的代码就是一系列字节——就是“字节码”。 + +A sequence of bytecode instructions: 0x00 HEALTH, 0x03 SOUND, 0x004 PARTICLES, ... + + + +To execute a single instruction, we see which primitive it is and dispatch to +the right API method: + +为了执行一条指令,我们看看它的原语是什么,然后调用正确的API方法。 + +^code interpret-instruction + +In this way, our interpreter forms the bridge between code world and data world. +We can wrap this in a little VM that executes an entire spell like so: + +用这种方式,我们的解释器建立了沟通代码世界和数据世界的桥梁。我们可以将一个执行法术的VM实现如下: + +^code vm + +Type that in and you'll have written your first virtual machine. Unfortunately, +it's not very flexible. We can't define a spell that touches the player's +opponent or lowers a stat. We can only play one sound! + +输入它,你就写好了你第一个虚拟机。不幸的是,它并不灵活。我们不能设定一个接触对手的法术,也不能减少状态值。我们只能播放一个声音! + +To get something that starts to have the expressive feel of an actual language, +we need to get parameters in here. + +为了有一点真正语言的感觉,我们需要在这里引入参数。 + +### A stack machine + +### 栈式机器 + +To execute a complex nested expression, you start with the innermost +subexpressions. You calculate those, and the results flow outward as arguments to +the expressions that contain them until eventually, the whole expression has been +evaluated. + +要执行复杂的嵌套表达式,得先从最里面的子表达式开始。你计算完里面的,结果向外作为参数流向 +包含它们的表达式,直到得出最终结果,整个表达式都算完了。 + +The Interpreter pattern models this explicitly as a tree of nested objects, but +we want the speed of a flat list of instructions. We still need to ensure +results from subexpressions flow to the right surrounding expressions. But, +since our data is flattened, we'll have to use the *order* of the instructions +to control that. We'll do it the same way your CPU does -- with a stack. + +解释器模式将这一明确的嵌套对象表现为树,但我们需要指令速度达到平面列表的速度。我们仍然需要确保 +子表达式的结果流向正确的表达式。 但是由于我们的数据是平面的,我们使用的指令*顺序*来控制这一点。我们用CPU同样的方式执行——用栈。 + + + +^code stack + +The VM maintains an internal stack of values. In our example, the only kinds of +values our instructions work with are numbers, so we can use a simple array +of `int`s. Whenever a bit of data needs to work its way from one instruction to +another, it gets there through the stack. + +虚拟机用内部栈保存值。在我们的例子中,我们的指令交互的值只有一种,那就是数字,所以我们可以使用简单的`int`数组。每比特数据需要从一条指令到另一条指令,它得通过堆栈。 + +Like the name implies, values can be pushed onto or popped off of the stack, so +let's add a couple of methods for that: + +顾名思义,值可以压入栈或者从栈弹出,所以让我们加一对方法。 + +^code push-pop + +When an instruction needs to receive parameters, it pops them off the stack like +so: + +当一条指令需要接受参数,它将参数从栈弹出,如下所示: + +^code pop-instructions + +To get some values *onto* that stack, we need one more instruction: a literal. +It represents a raw integer value. But where does *it* get its value from? How +do we avoid some turtles-all-the-way-down infinite regress here? + +为了将一些值*存入*栈中,我们需要另一条指令:字面量。它代表了一个原始的整数值。但是*它*的值又是从哪里来的呢?我们怎么样避免这样追根溯源到无穷无尽呢? + +The trick is to take advantage of the fact that our instruction stream is a +sequence of bytes -- we can stuff the number directly in the byte array. +We define another instruction type for a number literal like so: + +技巧是利用我们的指令路是一系列字节这一事实——我们可以直接将数值存储在字节数组中。如下,我们为数值字面量定义了另一条指令类型: + +^code interpret-literal + + + +Binary encoding of a literal instruction: 0x05 (LITERAL) followed by 123 (the value). + +It reads the next byte in the bytecode stream *as a +number* and pushes it onto the stack. + +它读取字节码流中的字节*作为数值*并将其压入栈。 + +Let's string a few of these instructions together and watch the interpreter +execute them to get a feel for how the stack works. We start with an empty stack +and the interpreter pointing to the first instruction: + +让我们串起来其中的几条指令看看解释器如何执行它们,来感受栈如何工作。我们从一个空栈开始,解释器指向第一个指令: + +Executing a bytecode sequence. The execution pointer points to the first literal instruction and the stack is empty. + +First, it executes the first `INST_LITERAL`. That reads the next byte from the +bytecode (`0`) and pushes it onto the stack: + +首先,他执行第一条`INST_LITERAL`。他读取字节码流的下一个字节(`0`)并压入栈中。 + +The next step. The literal 0 has been pushed onto the stack and the execution pointer is on the next literal. + +Then, it executes the second `INST_LITERAL`. That reads the `10` and pushes it: + +然后,他执行流第二条`INST_LITERAL`。读取`10`然后压入。 + +The next step. Now 10 has been pushed onto the stack and the execution pointer is at the Health instruction. + +Finally, it executes `INST_SET_HEALTH`. That pops `10` and stores it in +`amount`, then pops `0` and stores it in `wizard`. Then, it calls `setHealth()` +with those parameters. + +最后,他执行`INST_SET_HEALTH`。这弹出`10`存进`amount`,弹出`0`存进`wizard`。然后它用这两个参数调用`setHealth()`。 + +Ta-da! We've got a spell that sets the player's wizard's health to ten points. +Now, we've got enough flexibility to define spells that set either wizard's stats +to whatever amounts we want. We can also play different sounds and spawn +particles. + +完成!我们获得了将玩家巫师血量设为10点的法术。现在我们获得了足够的灵活度来定义修改任一巫师的状态到任意值的法术。我们还可以放不同的声音和粒子效果。 + +But... this still feels like a *data* format. We can't, for example, raise +a wizard's health by half of their wisdom. Our designers want to be able to +express *rules* for spells, not just *values*. + +但是……站感觉还是像*数据*格式。比如,我们不能将巫师的血量提升他智力的一半。我们的设计者想要有能力为法术设计*规则*,而不仅仅是*数值*。 + +### Behavior = composition + +### 行为 = 组合 + +If we think of our little VM like a programming language, all it supports now is +a couple of built-in functions and constant parameters for them. To get bytecode +to feel like *behavior*, what we're missing is *composition*. + +如果我们认为我们的小虚拟机像一门编程语言,现在支持的只有一些内置函数,以及调用用的参数。为了字节码 +感觉像*行为*,我们需要*组合*。 + +Our designers need to be able to create expressions that combine different +values in interesting ways. For a simple example, they want spells that modify a +stat *by* a certain amount instead of *to* a certain amount. + +我们的设计师需要能够创建以有趣的方式组合不同的值的表达式。举个简单的例子,他们希望一个法术*变化*一个数值而不是*修改到*一个数值。 + +That requires taking into account a stat's current value. We have instructions +for *writing* a stat, but we need to add a couple to *read* stats: + +这需要考虑到状态的当前值。我们有指令来*修改*状态,现在需要添加方法*读取*状态: + +^code read-stats + +As you can see, these work with the stack in both directions. They pop a +parameter to determine which wizard to get the stat for, and then they look up the stat's +value and push that back onto the stack. + +正如你所看到的,这些以两种凡是与堆栈交互。他们弹出一个参数来确定获取哪个巫师的状态,然后他们查找状态的值压入堆栈。 + +This lets us write spells that copy stats around. We could create a spell that +set a wizard's agility to their wisdom or a strange incantation that set one +wizard's health to mirror his opponent's. + +这允许我们写复制状态的法术。我们可以创建一个法术,以巫师的智慧设定敏捷度,或者一个法术让巫师的血量等于对方的。 + +Better, but still quite limited. Next, we need arithmetic. It's time our baby VM +learned how to add 1 + 1. We'll add a few more instructions. By now, you've +probably got the hang of it and can guess how they look. I'll just show +addition: + +有改进,但还是有限制。接下来,我们需要算术。现在是时候我们的小VM学习了如何计算1 + 1,我们将添加更多的指令。到现在为止,你可能已经知道如何去做,知道他们大概的模样。我只展示加法: + +^code add + +Like our other instructions, it pops a couple of values, does a bit of work, and +then pushes the result back. Up until now, every new instruction gave us an +incremental improvement in expressiveness, but we just made a big leap. It isn't +obvious, but we can now handle all sorts of complicated, deeply nested +arithmetic expressions. + +像我们的其他指令,它弹出一对数值,做一点工作,然后压入结果。直到现在,每一个新指令看起来在逐步改善,但其实我们完成一个大飞跃。这并不显而易见,但我们现在可以处理各种复杂的,深层嵌套的算术表达式。 + +Let's walk through a slightly more complex example. Say we want a spell that +increases the player's wizard's health by the average of their agility and +wisdom. In code, that's: + +让我们来看个稍微复杂点的例子。假设我们希望有法术将巫师的血量增加敏捷和智慧的平均值。用代码表示如下: + +^code increase-health + +You might think we'd need instructions to handle the explicit grouping that +parentheses give you in the expression here, but the stack supports that +implicitly. Here's how you could evaluate this by hand: + +你可能会认为我们需要指令来处理括号表达的分组,但栈隐式支持了这一点。你可以手算如下: + +1. Get the wizard's current health and remember it. +1. Get the wizard's agility and remember it. +2. Do the same for their wisdom. +3. Get those last two, add them, and remember the result. +4. Divide that by two and remember the result. +4. Recall the wizard's health and add it to that result. +5. Take that result and set the wizard's health to that value. + +1. 获取巫师当前的血量并记录结果。 +1. 获取巫师当前的血量并记录结果。 +2. 对智慧执行同样的操作。 +3. 获取最后两个值,加起来并记录结果。 +4. 除以二并记录结果。 +4. 回想巫师的血量,并它与这结果加和。 +5. 取出结果,设置巫师的血量为这一结果。 + +Do you see all of those "remembers" and "recalls"? Each "remember" corresponds +to a push, and the "recalls" are pops. That means we can translate this to +bytecode pretty easily. For example, the first line to get the wizard's current +health is: + +你看到这些“记录”和“回想”了吗?每个“记录”对应一个压入,“召回”对应弹出。这意味着我们可以很容易转化为字节码。例如,获得向导的当前血量的第一行: + + :::text + LITERAL 0 + GET_HEALTH + +This bit of bytecode pushes the wizard's health onto the stack. If we +mechanically translate each line like that, we end up with a chunk of bytecode +that evaluates our original expression. To give you a feel for how the +instructions compose, I've done that below. + +这些字节码将巫师的血量压入堆栈。 如果我们机械地将每个行都这样转化,我们最终得到一大块等价于我们原来表达式的字节码。为了让你感觉这些指令如何撰写的,我已经在下面完成了。 + +To show how the stack changes over time, we'll walk through a sample execution +where the wizard's current stats are 45 health, 7 agility, and 11 wisdom. Next +to each instruction is what the stack looks like after executing it and then a +little comment explaining the instruction's purpose: + +为了展示堆栈如何随着时间推移而变化,我们将使用一个样本来执行。巫师目前有45血量,7敏捷,和11智慧。 每条指令的旁边是栈在执行指令之后的模样,再旁边是一些解释了指令意图的注释: + + :::text + LITERAL 0 [0] # Wizard index + LITERAL 0 [0, 0] # Wizard index + GET_HEALTH [0, 45] # getHealth() + LITERAL 0 [0, 45, 0] # Wizard index + GET_AGILITY [0, 45, 7] # getAgility() + LITERAL 0 [0, 45, 7, 0] # Wizard index + GET_WISDOM [0, 45, 7, 11] # getWisdom() + ADD [0, 45, 18] # Add agility and wisdom + LITERAL 2 [0, 45, 18, 2] # Divisor + DIVIDE [0, 45, 9] # Average agility and wisdom + ADD [0, 54] # Add average to current health + SET_HEALTH [] # Set health to result + +If you watch the stack at each step, you can see how data flows through it +almost like magic. We push `0` for the wizard +index at the beginning, and it just hangs around at the bottom of the stack until +we finally need it for the last `SET_HEALTH` at the end. + +如果你注意力每一步的栈,你可以看到数据如何像魔法一样流动在其中。我们最开始压入`0`来查找巫师,然后它一直挂在栈的底部,直到最终的`SET_HEALTH`才用到它。 + + + +### A virtual machine + +### 一台虚拟机 + +I could keep going, adding more and more instructions, but this is a good place +to stop. As it is, we've got a nice little VM that lets us define fairly +open-ended behavior using a simple, compact data format. While "bytecode" and +"virtual machines" sound intimidating, you can see they're often as simple as a +stack, a loop, and a switch statement. + +我可以继续下去,添加越来越多的指令,但是时候适可而止了。如上所述,我们已经有了一个可爱的小虚拟机,可以让我们定义开放式的行为,使用简单,紧凑的数据格式。虽然“字节码”和“虚拟机”的听起来很吓人,你可以看到他们往往简单到了只需栈,循环,和switch语句。 + +Remember our original goal to have behavior be nicely sandboxed? Now that you've +seen exactly how the VM is implemented, it's obvious that we've accomplished +that. The bytecode can't do anything malicious or reach out into weird parts of +the game engine because we've only defined a few instructions that touch the +rest of the game. + +还记得我们最初让行为呆在沙盒中的目标吗?现在,你已经看到虚拟机是如何实现的,很明显,我们已经完成那个目标。字节码不能伸出恶意的触角到游戏引擎的其他部分,因为我们只定义了接触几个指令可以与剩下的比赛相关的指令。 + +We control how much memory it uses by how big of a stack we create, and we're +careful to make sure it can't overflow that. We can even control how much *time* it uses. In our instruction loop, +we can track how many we've executed and bail out if it goes over some limit. + +我们通过控制我们创造栈的大小来控制内存使用量,我们很小心地确保不会溢出。我们甚至可以控制它使用多少*时间*。在我们的指令循环里,我们可以追踪有多少指令已经执行,如果它遇到了问题我们也可以帮其摆脱困境。 + + + +There's just one problem left: actually creating the bytecode. So far, we've +taken bits of pseudocode and compiled them to bytecode by hand. Unless you've +got a *lot* of free time, that's not going to work in practice. + +这里还有一个问题:创建字节码。到目前为止,我们使用伪代码再手工编写为字节码。除非你有*很多*的空闲时间,这种方式并不实用。 + +### Spellcasting tools + +### 施法工具 + +One of our initial goals was to have a *higher*-level way to author behavior, +but we've gone and created something *lower*-level than C++. It has the runtime +performance and safety we want, but absolutely none of the designer-friendly +usability. + +我们的最初的目标是创造*高层*方式来控制行为,但是,我们却创造了比C++更*底层*的东西。它具有我们想要的运行性能和安全性,但绝对没有对设计师友好的可用性。 + +To fill that gap, we need some tooling. We need a program that lets users define +the high-level behavior of a spell and then takes that and generates the +appropriate low-level stack machine bytecode. + +为了填补这一空白,我们需要一些工具。我们需要一个程序,让用户定义法术的高层次的行为,然后需要的,并生成适当的低层次的栈式机器的字节码。 + +That probably sounds way harder than making the VM. Many programmers were +dragged through a compilers class in college and took away from it nothing but +PTSD triggered by the sight of a book with a dragon +on the cover or the words "[lex](http://en.wikipedia.org/wiki/Lex_(software))" +and "[yacc](http://en.wikipedia.org/wiki/Yacc)”. + +这可能听起来比虚拟机更难。许多程序员都在大学参加编译器课程并被龙书或者"[lex](http://en.wikipedia.org/wiki/Lex_(software))"和"[yacc](http://en.wikipedia.org/wiki/Yacc)”引发了PTSD。 + + + +In truth, compiling a text-based language isn't that bad, though it's a *bit* +too broad of a topic to cram in here. However, you don't have to do that. What I +said we need is a *tool* -- it doesn't have to be a *compiler* whose input +format is a *text file*. + +事实上,编译一个基于文本的语言并不那么糟,可能有*一点*宽泛的话题在这里需要补习研究。但是,你不需要那么做。我说,我们需要的是*工具*——它并不一定是一个*编译器*,其输入格式是*文本文件*。 + +On the contrary, I encourage you to consider building a graphical interface to +let users define their behavior, especially if the people using it won't be +highly technical. Writing text that's free of syntax errors is difficult for +people who haven't spent years getting used to a compiler yelling at them. + +相反,我建议你考虑构建一个图形界面让用户定义自己的行为,尤其是在使用它的人没有很强的技术。一个没有花几年时间习惯编译器怒吼的人很难写出没有语法错误文字。 + +Instead, you can build an app that lets users "script" by clicking and dragging +little boxes, pulling down menu items, or whatever else makes sense for the +kind of behavior you want them to create. + +相反,你可以建立一个应用程序,用户通过单击拖动小盒子,下拉菜单项,或任何你创造的有意义的行为创建“脚本”。 + + + +A mock-up of a little tree-based UI for authoring behavior. + + + +The nice thing about this is that your UI can make it impossible for users to +create "invalid" programs. Instead of vomiting error +messages on them, you can proactively disable buttons or provide default +values to ensure that the thing they've created is valid at all points in time. + +这样做的好处是,你的UI可以让用户无法创建的“无效的”程序。与其向他们喷洒错误警告,不如主动关闭按钮或提供默认值,以确保他们创造的东西在任何时间点上都有效。 + + + +This spares you from designing a grammar and writing a parser for a little +language. But, I know, some of you find UI programming equally unpleasant. Well, +in that case, I don't have any good news for you. + +这免去了设计语法和编写解析器的工作。但是,我知道,你会发现UI设计同样令人不快。好吧,在这种情况下,我没法提供给你你什么好消息。 + +Ultimately, this pattern is about expressing behavior in a user-friendly, +high-level way. You have to craft the user experience. To execute the behavior +efficiently, you then need to translate that into a lower-level form. It is real +work, but if you're up to the challenge, it can pay off. + +毕竟,这种模式是关于用对用户友好的高层方式表达行为。你必须设计用户体验。要有效地执行行为,需要转换成一个底层形式。这是必做的工作,但如果你准备好迎接挑战,最终会有所回报。 + +## Design Decisions + +## 设计决策 + +I tried to keep this chapter as simple as I could, +but what we're really doing is creating a language. That's a pretty open-ended +design space. Exploring it can be tons of fun, so make sure you don't forget to +finish your game. + +我尽可能让本章简单,但我们真正做的事情是创造一门语言。有很开放的设计空间,你可以从中获得很多乐趣,所以别忘了完成你的游戏。 + + + +### How do instructions access the stack? + +### 指令如何访问堆栈? + +Bytecode VMs come in two main flavors: stack-based and register-based. In a +stack-based VM, instructions always work from the top of the stack, like in our +sample code. For example, `INST_ADD` pops two values, adds them, and pushes the +result. + +有两种主要类型的字节码虚拟机:基于栈的和基于寄存器的。栈式虚拟机中,指令总是在栈的顶部工作,如同我们的示例代码所示。例如,`INST_ADD`弹出两个值,将它们相加,并将结果压入。 + +Register-based VMs still have a stack. The only difference is that instructions +can read their inputs from deeper in the stack. Instead of `INST_ADD` always *popping* +its operands, it has two indexes stored in the bytecode that identify where in +the stack to read the operands from. + +基于寄存器的虚拟机也有栈。唯一的不同是指令可以从栈的深处读取值。不像`INST_ADD`始终*弹出* +其操作数,它在字节码存储两个索引指示了从栈的何处读取操作数。 + + * **With a stack-based VM:** + + * **基于栈的虚拟机:** + + * *Instructions are small.* Since each instruction implicitly finds its + arguments on top of the stack, you don't need to encode any data for + that. This means each instruction can be pretty small, usually a single + byte. + + * *指令短小。*由于每个指令隐性认定其在栈顶部寻找参数,则不需要为之编码任何数据。这意味着每条指令可能会非常短,一般只需一个字节。 + + * *Code generation is simpler.* When you get around to writing the + compiler or tool that outputs bytecode, you'll find it simpler to + generate stack-based bytecode. Since each instruction implicitly works + from the top of the stack, you just need to output instructions in the + right order to pass parameters between them. + + * *代码容易生成。*当你需要为生成字节码写编译器或工具时,你会发现它更容易生成基于栈的字节码。由于每个指令隐性在栈顶部工作,你只需要以正确的顺序输出指令就可以在它们之间传递参数。 + + * *You have more instructions.* Each instruction only sees the very top of + the stack. This means that to generate code for something like `a = b + + c`, you need separate instructions to move `b` and `c` to the top of the + stack, perform the operation, then move the result into `a`. + + * *会生成更多的指令。*每条指令只能看到栈的最顶端。这意味着,产生用于像`a = b + c`这样的代码,你需要单独的指令将`b`和`c`压入栈的顶部,执行操作,再将结果压入`a`。 + + * **With a register-based VM:** + + * **基于寄存器的虚拟机:** + + * *Instructions are larger.* Since instructions need arguments for stack + offsets, a single instruction needs more bits. For example, an + instruction in Lua -- probably the most + well-known register-based VM -- is a full 32-bits. It uses 6 bits for + the instruction type, and the rest are arguments. + + * *指令较长。*由于指令需要参数做栈偏移量,单个指令需要更多的比特。例如,一个Lua指令——可能是最知名的的基于寄存器的虚拟机——使用一个完整的32位。它采用6位做指令类型,其余的是参数。 + + + + * *You have fewer instructions.* Since each instruction can do more work, + you don't need as many of them. Some say you get a performance + improvement since you don't have to shuffle values around in the stack + as much. + + * *您有较少的指令。*由于每一个指令可以做更多的工作,你并不需要那么多的指令。有人说,性能会得以提升,因为在栈中将值移来移去了。 + +So which should you do? My recommendation is to stick with a stack-based VM. +They're simpler to implement and much simpler to generate code for. +Register-based VMs got a reputation for being a bit faster after Lua converted +to that style, but it depends *deeply* on your actual instructions and on lots of +other details of your VM. + +所以,应该选哪一种?我的建议是坚持使用基于栈的虚拟机。他们更容易实现的,更容易的生成代码。Lua转换为基于寄存器的虚拟机从而变得更快,这为其博得了声誉,但是这*强烈*依赖于您的实际的指令和大量的虚拟机的其他细节。 + +### What instructions do you have? + +### 你有什么样的指令? + +Your instruction set defines the boundaries of what can and cannot be expressed +in bytecode, and it also has a big impact on the performance of your VM. Here's a +laundry list of the different kinds of instructions you may want: + +你的指令集定义了你在字节码中可以干什么,不能干什么的边界,它对你的虚拟机性能也有很大的影响。这里有一个清单,记录了不同种类的,你可能要的指令: + + * **External primitives.** These are the ones that reach out of the VM into + the rest of the game engine and do stuff that the user can see. They control + what kinds of real behavior can be expressed in bytecode. Without these, + your VM can't do anything more than burn CPU cycles. + + * **外部原语。**这是虚拟机可以与游戏引擎其他部分交互并影响玩家看到的部分。他们控制了字节码可以表达的真实行为。如果没有这些,你的虚拟机除了消耗CPU循环以外一无所得。 + + * **Internal primitives.** These manipulate values inside the VM -- things + like literals, arithmetic, comparison operators, and instructions that juggle + the stack around. + + * **内部原语**这些在虚拟机内操作数值——比如文字,算术,比较操作,以及操纵栈的指令。 + + * **Control flow.** Our example didn't cover these, but when you want behavior + that's imperative and conditionally executes instructions or loops and + executes instructions more than once, you need control flow. In the + low-level language of bytecode, they're surprisingly simple: jumps. + + * **控制流。**我们的例子并不包括这些,但是当你需要有条件执行或循环执行地行为,你需要控制流。   字节码这样底层的语言,他们出奇的简单:跳转。 + + In our instruction loop, we had an index to track where we were in the + bytecode. All a jump instruction does is modify that variable and change + where we're currently executing. In other words, it's a `goto`. You can + build all kinds of higher-level control flow using that. + + 在我们的指令循环中,我们有一个索引来跟踪我们到了字节码的哪里。跳转指令所做的是修改这个索引并改变我们将要执行的。换句话说,这是一个`goto`。您可以制定各种更高级别的控制流。 + + * **Abstraction.** If your users start defining a *lot* of stuff in data, + eventually they'll want to start reusing bits of bytecode instead of having + to copy and paste it. You may want something like callable procedures. + + **抽象。**如果你的用户开始在数据中定义*很多*的东西,最终他们要重用字节码的部分位,而不必复制和粘贴。您可能需要调用过程这样的东西。 + + In their simplest form, procedures aren't much more complex than a jump. The only + difference is that the VM maintains a second *return* stack. When it + executes a "call" instruction, it pushes the current instruction index onto + the return stack and then jumps to the called bytecode. When it hits a "return", + the VM pops the index from the return stack and jumps back to it. + +   最简单的形式中,过程并不比跳转复杂得多。唯一不同的是,在VM运行后者时,栈会存储第二次*返回*的位置。当执行“call”指令时,将当前指令索引压入栈中,然后跳转到被调用的字节码。当它到了“return”,虚拟机从堆栈弹出索引,然后调回索引指示的位置。 + +### How are values represented? + +### 数值是如何表示的? + +Our sample VM only works with one kind of value, integers. That makes answering this easy -- +the stack is just a stack of `int`s. A more full-featured VM will support +different data types: strings, objects, lists, etc. You'll have to decide how +those are stored internally. + +我们的样本虚拟机只与一种数值打交道:整数。回答这个问题很简单——栈只是一堆`int`。一个更加完整的虚拟机将支持不同的数据类型:字符串,对象,列表等。你必须决定如何在内部存储这些值。 + + * **A single datatype:** + +  * **单一数据类型:** + + * *It's simple.* You don't have to worry about tagging, conversions, or + type-checking. + + * *简单易用*你不必担心标记,转换,或类型检查。 + + * *You can't work with different data types.* This is the obvious downside. + Cramming different types into a single representation -- think storing + numbers as strings -- is asking for pain. + + * *无法使用不同的数据类型。*这是明显的缺点。将不同类型成塞进单一的表示方式——想想用存储字符串的方式存储数字——这是找打。 + + * **A tagged variant:** + + * **有标记的类型:** + + This is the common representation for dynamically typed languages. Every + value has two pieces. The first is a type tag -- an `enum` -- that identifies + what data type is being stored. The rest of the bits are then interpreted + appropriately according to that type, like: + + 这是动态类型语言中常见的表示法。所有的值有两部分。第一个是类型标记——一个`enum`——标识存储了什么数据类型。这些位的其余部分会被解释为这种类型: + + ^code tagged-value + + * *Values know their type.* The nice thing about this representation is + that you can check the type of a value at runtime. That's important for + dynamic dispatch and for ensuring that you don't try to perform operations on + types that don't support it. + +  * *数值知道其类型。*这个表示法的好处是您可以在运行时检查值的类型。这对动态分配是很重要的,可以确保你没有在类型上面执行其不支持的操作。 + + * *It takes more memory.* Every value has to carry around a few extra bits + with it to identify its type. In something as low-level as a VM, a few + bits here and there add up quickly. + +  *它需要更多的内存。*每个值都有随身携带一些额外的位 +        用它来识别其类型。在一些作为低级别作为VM,几 +        位在这里和那里迅速增加。 + + * **An untagged union:** + + * **未标记的union:** + + This uses a union like the previous form, but it does *not* have a type tag + that goes along with it. You have a little blob of bits that could represent + more than one type, and it's up to you to ensure you don't misinterpret + them. + + 这像前面一样使用union,但是它*没有*类型标识。你可以将这些位表示为不同的类型,由你确保没有搞错值的类型。 + + This is how statically typed languages represent + things in memory. Since the type system ensures at compile time that you + aren't misinterpreting values, you don't need to validate it at runtime. + + +    这是静态类型语言在内存中如何表示事物的方式。由于类型系统在编译期保证你没弄错值的类型,你不需要在运行时对其进行验证。 + + + + * *It's compact.* You can't get any more efficient than storing just the + bits you need for the value itself. + +     * *结构紧凑。*你找不到比只存储你需要的值更加有效率的存储方式。 + + * *It's fast.* Not having type tags implies you're not spending cycles + checking them at runtime either. This is one of the reasons + statically typed languages tend to be faster than dynamic ones. + +     * *速度快。*没有类型标识意味着你在运行时无需消耗周期检查它们的类型。这是静态类型语言往往比动态类型语言快的原因之一。 + + * *It's unsafe.* This is the real cost, of + course. A bad chunk of bytecode that causes you to misinterpret a value and + treat a number like a pointer or vice versa can violate the security of + your game or make it crash. + +    *不安全的。*这是真正的代价,当然。一块坏的字节码,让你把值误解为指针,会打破你游戏的安全或使其崩溃。 + + + + * **An interface:** + + * **接口:** + + The object-oriented solution for a value that maybe be one of several + different types is through polymorphism. An interface provides virtual + methods for the various type tests and conversions, along the lines of: + +    多种类型值的面向对象的解决方案是通过多态。接口提供虚拟方法为不同的类型测试和转换,如下: + + ^code value-interface + + Then you have concrete classes for each specific data type, like: + +    然后你为每个特定的数据类型设计特定的类,如: + + ^code int-value + + * *It's open-ended.* You can define new value types outside of the core VM + as long as they implement the base interface. + +     * *开放。*您可以在虚拟机的核心之外定义新的值类型,只要它们实现了基本接口就行。 + + * *It's object-oriented.* If you adhere to OOP principles, this does + things the "right" way and uses polymorphic dispatch for type-specific + behavior instead of something like switching on a type tag. + +    * *面向对象。*如果你坚持OOP原则,这是做事情的“正确”的方式,使用多态为特定类型分配行为,而不是标签交换之类的。 + + * *It's verbose.* You have to define a separate class with all of the + associated ceremonial verbiage for each data type. Note that in the + previous examples, we showed the entire definition of *all* of the value + types. Here, we only cover one! + +    * *冗长。*您必须定义一个单独的类,包含了每个数据类型的相关行为。注意,在前面的例子中,整个定义定义了*所有*的类型。在这里,我们只覆盖了一个! + + * *It's inefficient.* To get polymorphism, you have to go through a + pointer, which means even tiny values like Booleans and numbers get + wrapped in objects that are allocated on the heap. Every time you touch + a value, you have to do a virtual method call. + +    * *低效。*为了多态,你必须使用指针,这意味着即使是短小的值,如布尔和数字,得裹在分配在堆中的对象里。你每使用一个值,你就得做一次虚方法调用。 + + In something like the core of a virtual machine, small performance hits + like this quickly add up. In fact, this suffers from many of the + problems that caused us to avoid the Interpreter pattern, except now the + problem is in our *values* instead of our *code*. + +       在虚拟机核心之类的地方,小的性能影响像这样迅速叠加。事实上,这引起了许多我们试图避免的解释器模式中的问题。除了现在的问题不在我们的*代码*中,而是在我们的*值*中。 + +My recommendation is that if you can stick with a single data type, do that. Otherwise, +do a tagged union. That's what almost every language interpreter in the world +does. + +我的建议是,如果你可以只用单一数据类型,那就这么做。除此以外,使用标记的union。这是世界上几乎每一个语言解释器做的事情。 + +### How is the bytecode generated? + +### 如何生成字节码? + +I saved the most important question for last. I've walked you through the code +to *consume* and *interpret* bytecode, but it's up to you to build something to +*produce* it. The typical solution here is to write a compiler, but it's not the +only option. + +我将最重要的问题留到最后。我们已经完成了*消耗*和*解释*字节码的代码,但需要你写些*制造*字节码的工具。典型的解决方案是写一个编译器,但它不是唯一的选择。 + + * **If you define a text-based language:** + + * **如果你定义一个基于文本的语言:** + + * *You have to define a syntax.* Both amateur and professional language + designers categorically underestimate how difficult this is to do. + Defining a grammar that makes parsers happy is easy. Defining one that + makes *users* happy is *hard*. + +     * *你必须定义语法。*业余和专业的语言设计师小看这件困难的事情。让解析器快乐很简单,让*用户*快乐很*难*。 + + Syntax design is user interface design, and that process doesn't get + easier when you constrain the user interface to a string of characters. + +      语法设计是用户界面设计,当你将用户界面限制到字符构成的字符串,这可没把事情变简单。 + + * *You have to implement a parser.* Despite their reputation, this part is + pretty easy. Either use a parser generator like ANTLR or Bison, or -- + like I do -- hand-roll a little recursive descent one, and you're good to + go. + +     * *你必须实现解析器。*不管他们的名声,这部分其实非常的简单。无论是使用ANTLR或Bison,还是——像我做的那样——手写递归下降,都可以完成。 + + * *You have to handle syntax errors.* This is one of the most important + and most difficult parts of the process. When users make syntax and + semantic errors -- which they will, constantly -- it's your job to guide + them back onto the right path. Giving helpful feedback isn't easy when + all you know is that your parser is sitting on some unexpected + punctuation. + +     * *您必须处理语法错误。*这是最重要和最困难的部分。当用户制造了语法和语义错误——他们总会这么干——这是你的任务引导他们返回到正确的道路。只知道解析器接到意外的符号,给予有用的的反馈并不容易。 + + * *It will likely turn off non-technical users.* We programmers like text + files. Combined with powerful command-line tools, we think of them as + the LEGO blocks of computing -- simple, but easily composable in a million + ways. + +     * *它可能会对非技术用户关上大门。*我们程序员喜欢的文本文件。结合强大的命令行工具,我们把它们当作计算用的乐高积木——简单,易于用百万种方式组合 + + Most non-programmers don't think of plaintext like that. To them, text + files feel like filling in tax forms for an angry robotic auditor that yells at + them if they forget a single semicolon. + +        非程序员大部分不这样想文本。对他们来说,文本文件感觉就像为愤怒机器人审核员填写税表,如果他们忘记了一个分号就会痛斥他们。 + + * **If you define a graphical authoring tool:** + + * **如果你定义了一个图形化创作工具:** + + * *You have to implement a user interface.* Buttons, clicks, drags, stuff + like that. Some cringe at the idea of this, but I personally love it. If + you go down this route, it's important to treat designing the user + interface as a core part of doing your job well -- not just an + unpleasant task to be muddled through. + +    * *您必须实现用户界面。*按钮,点击,拖动,诸如此类。有些人畏惧它,但我喜欢它。如果你沿着这条路走下去,设计用户界面和你的工作的核心部分同等重要——而不是一个硬着头皮完成的乱七八糟的工作。 + + Every little bit of extra work you do here will make your tool easier + and more pleasant to use, and that directly leads to better content in + your game. If you look behind many of the games you love, you'll often + find the secret was fun authoring tools. + +        每一点额外的工作都会让你的工具更容易更舒适的使用,并直接导致了你的游戏能有更好的内容。如果你看看很多你喜欢的游戏的内部,你经常会发现制作有趣的创造工具是秘诀之一。 + + * *You have fewer error cases.* Because the user is building behavior + interactively one step at a time, your application can guide them away + from mistakes as soon as they happen. + +     * *您有较少的错误情况。*由于用户通过交换式一步一步地设计行为,你的应用程序尽快可以引导他们走出错误。 + + With a text-based language, the tool doesn't see *any* of the user's + content until they throw an entire file at it. That makes it harder to + prevent and handle errors. + +     使用基于文本的语言,该工具直到用户输入整个文件*才能*看到用户的内容。这使得它更难预防和处理错误。 + + * *Portability is harder.* The nice thing about text compilers is that + text files are universal. A simple compiler + just reads in one file and writes one out. Porting that across operating + systems is trivial. + + * *更难实现便携性。*文本编译器的好处是,文本文件是通用的。编译器简单的读入文件并写出。跨平台移植的工作实在微不足道。 + + + + When you're building a UI, you have to choose which framework to use, + and many of those are specific to one OS. There are cross-platform UI + toolkits too, but those often get ubiquity at the expense of + familiarity -- they feel equally foreign on all of platforms. + +       当你构建用户界面,你必须选择要使用的架构,其中很多是基于一个操作系统。也有跨平台的用户界面工具包,但他们往往会付出对所有平台适用的代价——他们对所有的平台上同样不适用。 + +## See Also + +## 参见 + + * This pattern's close sister is the Gang of Four's Interpreter pattern. Both give you a way to express + composable behavior in terms of data. + + * 这一章节的近亲是GoF的解释器模式。两种方式都能让你用数据组合行为。 + + In fact, you'll often end up using *both* patterns. The tool you use to + generate bytecode will have an internal tree of objects that represents the + code. This is exactly what the Interpreter pattern expects. + + 事实上,最终你两种模式*都*会使用。你用来构造字节码的工具会有内部的对象树。这也是解释器模式所预料的。 + + In order to compile that to bytecode, you'll recursively walk the tree, just + like you do to interpret it with the Interpreter pattern. The *only* + difference is that instead of executing a primitive piece of behavior + immediately, you output the bytecode instruction to perform that later. + + 为了编译到字节码,你需要递归回溯整棵树,就像你用解释器模式去解释它一样。*唯一的*不同在于不是立即的执行一段行为,你生成了整个字节码然后再去执行。 + + * The [Lua](http://www.lua.org/) programming language is the most widely used + scripting language in games. It's implemented internally as a very compact + register-based bytecode VM. + + * [Lua](http://www.lua.org/)是游戏中最被广泛应用的脚本语言。它的内部被实现为一个非常紧凑的,基于寄存器的字节码虚拟机。 + + * [Kismet](http://en.wikipedia.org/wiki/UnrealEd#Kismet) is a graphical + scripting tool built into UnrealEd, the editor for the Unreal engine. + + * [Kismet](http://en.wikipedia.org/wiki/UnrealEd#Kismet)是个可视化脚本编辑工具,应用于Unreal引擎的编辑器UnrealEd。 + + * My own little scripting language, + [Wren](https://github.com/munificent/wren), is a simple stack-based bytecode + interpreter. + + * 我的脚本语言[Wren](https://github.com/munificent/wren),是一个简单的,基于栈的字节码解释器。 + \ No newline at end of file diff --git a/book/command.markdown b/book/command.markdown new file mode 100644 index 0000000..e589fef --- /dev/null +++ b/book/command.markdown @@ -0,0 +1,588 @@ +^title Command +^section Design Patterns Revisited + +title 命令模式 +section 重访设计模式 + +Command is one of my favorite patterns. Most large programs I write, games or +otherwise, end up using it somewhere. When I've used it in the right place, it's +neatly untangled some really gnarly code. For such a swell pattern, the Gang of +Four has a predictably abstruse description: + +命令模式是我最喜欢的模式之一。 +大多数我写的大程序,游戏或者别的什么,都会在某处用到它。 +当在正确的地方使用时,它可以将复杂的代码简洁的展开。 +对于这样一个了不起的模式,不出所料,GoF有一个的深奥定义: + +> Encapsulate a request as an object, thereby letting users parameterize clients +> with different requests, queue or log requests, and support undoable +> operations. + +> 将请求封装为一个对象,这样就允许使用者用请求参数化客户, +> 将请求排队或者记入日志,并支持撤销请求。 + +I think we can all agree that that's a terrible sentence. First of all, it +mangles whatever metaphor it's trying to establish. Outside of the weird world +of software where words can mean anything, a "client" is a *person* -- someone +you do business with. Last I checked, human beings can't be "parameterized". + +我想我们可以讨论一下这是不是一个糟糕的句子。 +第一,它损坏了它想建立的比喻。 +在软件之外的狂野世界,词语可以指代任何事物,一个“客户”是一个*人*——那些你与之做生意的人。上次我检查的时候,人类还不能被“参数化”。 + +Then, the rest of that sentence is just a list of stuff you could maybe possibly +use the pattern for. Not very illuminating unless your use case happens to be in +that list. *My* pithy tagline for the Command pattern is: + +然后,句子余下的部分介绍了一些你可能会使用这个模式的原因。 +如果你的情况不在这个列表中,那么这对你就不是很有启发。 +*我的*精简的命令模式定义为: + +**A command is a *reified method call*.** + +**命令式一个*具体的方法调用*。** + + + +Of course, "pithy" often means "impenetrably terse", so this may not be much of +an improvement. Let me unpack that a bit. "Reify", in case you've never heard +it, means "make real". Another term for reifying is making something "first-class". + +当然,“精简”往往意味着“损失信息的简洁”,所以这可能没有太大的改善。 +让我扩展一下。“具体的”,如果你没有听说过的话,它的意思是“变得具体”。 +另外一种变得具体的表达方式方式是将某事物作为第一公民对待。 + + + +Both terms mean taking some *concept* and turning +it into a piece of *data* -- an object -- that you can stick in a variable, pass +to a function, etc. So by saying the Command pattern is a "reified method call", +what I mean is that it's a method call wrapped in an object. + +两种表达方式都意味着将一些概念变成数据——一个对象——可以存储在一个变量中,传给一个函数之类的。 +所以称命令模式为一种“具体的方法调用”,意味着方法调用被存储在一个对象中。 + +That sounds a lot like a "callback", "first-class function", "function pointer", +"closure", or "partially applied function" depending on which language you're +coming from, and indeed those are all in the same ballpark. The Gang of Four +later says: + +这听起来有些像“回调”,“第一公民函数”,“函数指针”,“闭包”,“偏函数”,这取决于你在学哪种语言,事实上他们大致上都是同一个东西。GoF随后说: + +> Commands are an object-oriented replacement for callbacks. + +> 命令模式是一种取代回调的面向对象实现。 + +That would be a better slugline for the pattern than the one they chose. + +这是一种对命令模式更好的解释。 + +But all of this is abstract and nebulous. I like to start chapters with +something concrete, and I blew that. To make up for it, from here on out it's +all examples where commands are a brilliant fit. + +但所有的这些都既抽象又模糊。我喜欢用实际的东西作为章节的开始,我搞砸了。为了弥补,从这里开始都是命令模式能出色应用的例子。 + +## Configuring Input + +## 设定输入 + +Somewhere in every game is a chunk of code that reads in raw user input -- +button presses, keyboard events, mouse clicks, whatever. It takes each input and +translates it to a meaningful action in the game: + +在每个游戏中都有一块代码读取用户的输入——按钮按下,键盘敲击,鼠标点击,诸如此类的。这块代码会获取用户的输入然后将其变为游戏中有意义的行为。 + +A controller, with A mapped to swapWeapon(), B mapped to lurch(), X mapped to jump(), and Y mapped to fireGun(). + +A dead simple implementation looks like: + +一种简单死了的实现会是这样: + + + +^code handle-input + + + +This function typically gets called once per frame by the Game Loop, and I'm sure you can figure out what it +does. This code works if we're willing to hard-wire user inputs to game actions, +but many games let the user *configure* how their buttons are mapped. + +这个函数通常在游戏循环的每帧后会被调用一次,我确信你可以理解它做了什么。这代码在我们想将用户的输入和程序行为硬编码在一起时可以正常工作,但是许多游戏运行玩家设定他们按键的功能。 + +To support that, we need to turn those direct calls to `jump()` and `fireGun()` +into something that we can swap out. "Swapping out" sounds a lot like assigning +a variable, so we need an *object* that we can use to represent a game action. +Enter: the Command pattern. + +为了支持这一点,我们需要将这些jump()和fireGun()的直接调用转化为我们可以换出去的东西。“换出去”听起来有点像声明变量,因此我们需要一个用来表示游戏行为的对象。进入:命令模式。 + +We define a base class that represents a triggerable game command: + +我们定义了一个基本类代表可以触发的游戏命令: + + + +^code command + + + +Then we create subclasses for each of the different game actions: + +然后我们为不同的游戏行为定义相应的子类: + +^code command-classes + +In our input handler, we store a pointer to a command for each button: + +在我们的输入控制器,我们为每个键存储一个指向一条命令的指针。 + +^code input-handler-class + +Now the input handling just delegates to those: + +现在输入处理交给了这些: + + + +^code handle-input-commands + + + +Where each input used to directly call a function, now there's a layer of +indirection: + +当每一个输入直接调用函数,这里会有一层间接寻址: + +A controller, with each button mapped to a corresponding 'button_' variable which in turn is mapped to a function. + +This is the Command pattern in a nutshell. If you can see the merit of it +already, consider the rest of this chapter a bonus. + +这是命令模式的简短介绍。如果你能够看出它的好处,就把这章剩下的部分作为奖励吧。 + +## Directions for Actors + +## 角色指导 + +The command classes we just defined work for the previous example, but they're +pretty limited. The problem is that they assume there are these top-level +`jump()`, `fireGun()`, etc. functions that implicitly know how to find the +player's avatar and make him dance like the puppet he is. + +我们刚才定义的类可以在之前的例子上正常工作,但局限很大。问题在于假设那些顶层的`jump()`, `fireGun()`之类的。默认了函数可以找到玩家的角色,然后让他像木偶一样跳舞。 + +That assumed coupling limits the usefulness of those commands. The *only* thing +the `JumpCommand` can make jump is the player. Let's loosen that restriction. +Instead of calling functions that find the commanded object themselves, we'll +*pass in* the object that we want to order around: + +这些假定的耦合限制了这些命令的用处。`JumpCommand`*只能*让玩家的角色跳跃。让我们放松这个限制。不让函数去找他们控制的角色,我们将控制的角色对象传进去: + +^code actor-command + +Here, `GameActor` is our "game object" class that represents a character in the +game world. We pass it in to `execute()` so that the derived command can invoke +methods on an actor of our choice, like so: + +这里`GameActor`是代表了游戏世界中一个角色的“游戏对象”类。我们将其传给`execute()`,这样我们可以在其子类中添加函数,来与我们选择的角色关联,就像这样: + +^code jump-actor + +Now, we can use this one class to make any character in the game hop around. +We're just missing a piece between the input handler and the command that takes +the command and invokes it on the right object. First, we change `handleInput()` +so that it *returns* commands: + +现在我们可以使用这个类让游戏中的任何角色跳来跳去了。我们在输入控制和命令在正确的对象上起作用中还少了一块。第一,我们修改`handleInput()`这样它可以*返回*命令: + +^code handle-input-return + +It can't execute the command immediately since it doesn't know what actor to +pass in. Here's where we take advantage of the fact that the command is a +reified call -- we can *delay* when the call is executed. + +这里不能立即执行因为我们还不知道哪一个角色会传送到这里。这里我们享受了命令是一种具体调用的好处——我们可以`延迟`到调用被执行的时候才知道是哪个角色。 + +Then, we need some code that takes that command and runs it on the actor +representing the player. Something like: + +然后,我们需要一些代码接受这条命令然后在玩家角色上起作用。像这样: + +^code call-actor-command + +Assuming `actor` is a reference to the player's character, this correctly drives +him based on the user's input, so we're back to the same behavior we had in the +first example. But adding a layer of indirection between the command and the +actor that performs it has given us a neat little ability: *we can let the +player control any actor in the game now by changing the actor we execute +the commands on.* + +将`actor`视为玩家角色的一个引用,它会正确的按着玩家的输入移动,所以我们获得了在第一个例子中相同的行为。但是在命令和角色间增加了一层重定向,我们获得了一个灵巧的功能:*我们可以让玩家控制游戏中的任何角色,只需改变我们在那个角色上执行代码。* + +In practice, that's not a common feature, but there is a similar use case that +*does* pop up frequently. So far, we've only considered the player-driven +character, but what about all of the other actors in the world? Those are driven +by the game's AI. We can use this same command pattern as the interface between +the AI engine and the actors; the AI code simply emits `Command` objects. + +在实践中,这并不是一个常用的功能,但是这*经常*会有类似的用例跳出来。 +到目前为止,我们只考虑了玩家控制的角色,但是游戏中的其他角色呢? +它们被游戏AI控制。我们可以在AI和角色之间使用相同的命令模式;AI代码只是简单的放出`Command`对象。 + +The decoupling here between the AI that selects commands and the actor code +that performs them gives us a lot of flexibility. We can use different AI +modules for different actors. Or we can mix and match AI for different kinds of +behavior. Want a more aggressive opponent? Just plug-in a more aggressive AI to +generate commands for it. In fact, we can even bolt AI onto the *player's* +character, which can be useful for things like demo mode where the game needs to +run on auto-pilot. + +在选择命令的AI和展现命令的游戏角色间解耦给了我们很大的灵活度。我们可以对不同的角色使用不同的AI。或者我们可以为了不同的行为而混合AI。想要一个更加有攻击性的同伴?插入一个更加有攻击性的AI为其生成命令。事实上,我们甚至可以为*玩家角色*加上一个AI,这在原型阶段游戏需要一个自动驾驶员是很有用的。 + +By making the commands that control an actor +first-class objects, we've removed the tight coupling of a direct method call. +Instead, think of it as a queue or stream of commands: + +把控制角色的命令变为第一公民对象,我们已处理直接方法调用中严厉的束缚。取而代之的是,将其想象为命令队列,或者是命令流: + + + + + +A pipe connecting AI to Actor. + + + +Some code (the input handler or AI) produces +commands and places them in the stream. Other code (the dispatcher or actor +itself) consumes commands and invokes them. By sticking that queue in the +middle, we've decoupled the producer on one end from the consumer on the other. + +一些代码(输入控制器或者AI)产生一系列指令然后将其放入流中。另一些指令(调度器或者角色自身)消耗指令并调用他们。通过在中间加入了一个队列,我们解耦了消费者和生产者。 + + + +## Undo and Redo + +## 撤销和重做 + +The final example is the most well-known use of this pattern. If a command object +can *do* things, it's a small step for it to be able to *undo* them. Undo is +used in some strategy games where you can roll back moves that you didn't like. +It's *de rigueur* in tools that people use to *create* games. The surest way to make your game designers hate you is giving +them a level editor that can't undo their fat-fingered mistakes. + +最后的这个例子是这种模式最广为人知的使用情况。如果一个命令对象可以*做*一件事,那么它亦可以*撤销*这件事。在一些策略游戏中使用撤销,这样你就可以回滚那些你不喜欢的操作。在人们*创造*游戏时,这是*必不可少的*工具。一个不能撤销他们肥手指导致的错误的编辑器,肯定会让游戏设计者恨你。 + + + +Without the Command pattern, implementing undo is surprisingly hard. With it, +it's a piece of cake. Let's say we're making a single-player, turn-based game and +we want to let users undo moves so they can focus more on strategy and less on +guesswork. + +没有了命令模式,实现撤销非常困难,有了它,就是小菜一碟。假设我们在制作单人回合制游戏,我们想让玩家能撤销移动,遮掩他们就可以集中注意力在策略上而不是猜测上。 + +We're conveniently already using commands to abstract input handling, so every +move the player makes is already encapsulated in them. For example, moving a +unit may look like: + +我们已经使用了命令来抽象输入控制,所以每一个玩家的举动都已经被封装其中。举个例子,移动一个单位的代码可能如下: + +^code move-unit + +Note this is a little different from our previous commands. In the last example, +we wanted to *abstract* the command from the actor that it modified. In this +case, we specifically want to *bind* it to the unit being moved. An instance of +this command isn't a general "move something" operation that you could use in a +bunch of contexts; it's a specific concrete move in the game's sequence of +turns. + +注意这和前面的命令有些许不同。在前面的例子中,我们需要从他修改的角色那里抽取命令。在这个例子中,我们想将命令绑定到我们要移动的单位上。这条命令的一个实例不是一个通用的“移动某物”指令,而是一个游戏回合中特殊的一次移动。 + +This highlights a variation in how the Command pattern gets implemented. In some +cases, like our first couple of examples, a command is a reusable object that +represents a *thing that can be done*. Our earlier input handler held on to a +single command object and called its `execute()` method anytime the right button +was pressed. + +这集中展示了命令模式可以被实现的几种情况。在一些情况中,就像我们第一个例子中,一条指令是可以重用的对象,代表了*可以执行的事件*。我们早期的输入控制将其实现一个命令对象,然后在按键被按下的时候调用其`execute()`方法 + +Here, the commands are more specific. They represent a thing that can be done at +a specific point in time. This means that the input handling code will be *creating* an instance of this every time the player chooses +a move. Something like: + +这里的命令更加特殊。他们代表了特定时间点能做的特定事件。这意味着输入控制代码可以在玩家下决定时*创造*一个实例。就像这样: + +^code get-move + + + +The fact that commands are one-use-only will come to our advantage in a second. +To make commands undoable, we define another operation each command class needs +to implement: + +指令是一次性意味着我们很快的得到了一个优点。为了让指令可以被取消,我们为每一个类定义一个方法。 + +^code undo-command + +An `undo()` method reverses the game state changed by the corresponding +`execute()` method. Here's our previous move command with undo support: + +`undo()`方法回滚了`execute()`方法造成的游戏状态改变。这里是我们添加了撤销后的移动指令 + +^code undo-move-unit + +Note that we added some more state to the class. +When a unit moves, it forgets where it used to be. If we want to be able to undo +that move, we have to remember the unit's previous position ourselves, which is +what `xBefore_` and `yBefore_` do. + +注意我们为类添加了一些状态。当单位移动时,它忘记了它执勤啊是什么样的。如果我们想要撤销这个移动,我们需要记得单位之前的状态,也就是`xBefore_`和`yBefore_`做的事。 + + + +To let the player undo a move, we keep around the last command they executed. +When they bang on Control-Z, we call that command's `undo()` method. (If they've +already undone, then it becomes "redo" and we execute the command again.) + +为了让玩家撤销一个决定,我们记录了他们执行的最后一个操作。当他们按下`control+z`时,我们调用命令的`undo()`方法。(如果他们已经撤销了,那么就变成了“重做”,我们会再一次执行命令。) + +Supporting multiple levels of undo isn't much harder. Instead of remembering the +last command, we keep a list of commands and a reference to the "current" one. +When the player executes a command, we append it to the list and point "current" +at it. + +支持多层的撤销也不是更难。我们不单单记录最后一条指令,还要记录一个指令列表,然后一个引用指向“现在”。当玩家执行一条命令,我们将其添加到列表然后将代表“现在”的指针指向它。 + +A stack of commands from older to newer. A 'current' arrow points to one command, an 'undo' arrow points to the previous one, and 'redo' points to the next. + +When the player chooses "Undo", we undo the current command and move the current +pointer back. When they choose "Redo", we advance the +pointer +and then execute that command. If they choose a new command after undoing some, +everything in the list after the current command is discarded. + +当玩家选择“撤销”,我们撤销现在的指令并将代表现在的指针往后退。当他们选择“重做”我们将代表现在的指针往前进并执行该指令。如果他们在撤销后选择了一条新指令,那么指令列表代表现在的指针所指之后的部分就全部清除了。 + +The first time I implemented this in a level editor, I felt like a genius. I was +astonished at how straightforward it was and how well it worked. It takes +discipline to make sure every data modification goes through a command, but once +you do that, the rest is easy. + +第一次我在一个关卡编辑器中实现这一点时,我觉得自己简直就是一个天才。我惊讶与它如此的简明有效。你需要约束保证每一个数据修改都通过一条指令完成,但是一旦你做到了,余下的都很简单。 + + + +## Classy and Dysfunctional? + +## 优雅但是不正常? + +Earlier, I said commands are similar to first-class functions or closures, but +every example I showed here used class definitions. If you're familiar with +functional programming, you're probably wondering where the functions are. + +早些时候,我说过命令与第一公民函数或者闭包类似,但是在这里我展现的每一个例子都是通过类完成的。如果你更熟悉函数式编程,你也许会疑惑函数都在哪里。 + +I wrote the examples this way because C++ has pretty limited support for +first-class functions. Function pointers are stateless, functors are weird and +still +require defining a class, and the lambdas in C++11 are tricky to work with +because of manual memory management. + +我用这种方式写例子是因为C++对第一公民函数支持非常有限。函数指针没有状态,函子很奇怪而且仍然需要定义类,在C++11中的lambda演算需要大量的人工记忆来使用。 + +That's *not* to say you shouldn't use functions for the Command pattern in other +languages. If you have the luxury of a language with real closures, by all means, +use them! In some ways, the Command pattern is a way of +emulating closures in languages that don't have them. + +这并*不是*说你在其他语言中不可以使用函数来完成命令。如果你使用的语言非常奢侈的支持闭包,不管怎样,快去用它!在某种程度上,命令模式是为一些没有闭包的语言模拟闭包。 + + + +For example, if we were building a game in JavaScript, we could create a move +unit command just like this: + +举个例子,如果我们使用javascript来写游戏,那么我们可以用这种方式来写让单位移动的命令: + + :::javascript + function makeMoveUnitCommand(unit, x, y) { + // This function here is the command object: + return function() { + unit.moveTo(x, y); + } + } + +We could add support for undo as well using a pair of closures: + +我们可以通过一对闭包来为撤销提供支持: + + :::javascript + function makeMoveUnitCommand(unit, x, y) { + var xBefore, yBefore; + return { + execute: function() { + xBefore = unit.x(); + yBefore = unit.y(); + unit.moveTo(x, y); + }, + undo: function() { + unit.moveTo(xBefore, yBefore); + } + }; + } + +If you're comfortable with a functional style, this way of doing things is +natural. If you aren't, I hope this chapter helped you along the way a bit. For +me, the usefulness of the Command pattern really shows how effective the +functional paradigm is for many problems. + +如果你习惯了函数式编程,那么这种做事的方法对你来说是很自然的。如果你没有,我希望这一章可以帮到你一些。对于我来说,命令模式的作用展现了功能范式的高效。 + +## See Also + +## 参见 + + * You may end up with a lot of different command classes. In order to make it + easier to implement those, it's often helpful to define a concrete base + class with a bunch of convenient high-level methods that the derived + commands can compose to define their behavior. That turns the command's main + `execute()` method into a Subclass Sandbox. + + * 你可能会得到很多不同的命令类。为了使之更容易实现,定义一个具体基类,包括一些能定义自己行为的高层方法,往往有帮助。这将命令的主体`execute()`转到子类沙箱中。 + + * In our examples, we explicitly chose which actor would handle a command. In + some cases, especially where your object model is hierarchical, it may not + be so cut-and-dried. An object may respond to a command, or it may decide to + pawn it off on some subordinate object. If you do that, you've got yourself + a Chain of Responsibility. + + * 在我们的例子中,我们明确地选择了哪一个角色会处理命令。在某些情况下,特别是当你的对象模型是分层的,也可以不这么简单粗暴。对象可以响应命令,或它可以决定将其交给一些它的从属对象。如果你这样做,你就完成了一个职责链模式。 + + * Some commands are stateless chunks of pure behavior like the `JumpCommand` + in the first example. In cases like that, having more than one instance of that class wastes memory + since all instances are equivalent. The Flyweight pattern addresses that. + + * 有些命令是纯粹的行为,类似第一个例子中的`JumpCommand`。在这种情况下,有多个实例是在浪费内存,因为所有的实例是等价的。可以用享元模式解决。 + + \ No newline at end of file diff --git a/book/component.markdown b/book/component.markdown new file mode 100644 index 0000000..9f4c233 --- /dev/null +++ b/book/component.markdown @@ -0,0 +1,939 @@ +^title Component +^section Decoupling Patterns + +组件模式 +解耦模式 + +## Intent + +## 意图 + +*Allow a single entity to span multiple domains without coupling the domains to +each other.* + +*允许单一的实体跨越多个域,无需将这些域耦合彼此。* + +## Motivation + +## 动机 + +Let's say we're building a platformer. The Italian plumber demographic is +covered, so ours will star a Danish baker, Bjørn. It +stands to reason that we'll have a class representing our friendly pastry chef, +and it will contain everything he does in the game. + +比方说,我们正在构建平台跳跃游戏。意大利水管工已经有人做了,因此我们将出动丹麦的面包师,Bjorn。照理说,会有有一个类来表示友好的糕点厨师,将包含他在比赛中做的一切。 + + + +Since the player controls him, that means reading controller input and +translating that input into motion. And, of course, he needs to interact with the level, +so some physics and collision go in there. Once that's done, he's got to show +up on screen, so toss in animation and rendering. He'll probably play some +sounds too. + +由于玩家控制他,这意味着读取控制器的输入然后转化为运动。而且,当然,他需要与关卡相互作用, +所以要引入物理和碰撞。一旦这样做了,他必须在屏幕上出现,所以要引入动画和渲染。他可能还会播放一些声音。 + +Hold on a minute; this is getting out of control. Software Architecture 101 +tells us that different domains in a program should be kept isolated from each +other. If we're making a word processor, the code that handles printing +shouldn't be affected by the code that loads and saves documents. A game doesn't +have the same domains as a business app, but the rule still applies. + +等一下;这在失控。软件体系结构101课程告诉我们,在一个程序不同域应保持分离。如果我们做一个文字处理器,处理打印的代码不应该受加载和保存文件的代码影响。一个游戏和企业应用程序没有相同的域,但该规则仍然适用。 + +As much as possible, we don't want AI, physics, rendering, sound and other +domains to know about each other, but now we've got all of that crammed into one +class. We've seen where this road leads to: a 5,000-line dumping ground source +file so big that only the bravest ninja coders on your team even dare to go in +there. + +我们希望AI,物理,渲染,声音和其他域尽可能不相互了解,但现在我们将所有这一切挤在一个类中。我们已经看到了这条路通往何处:5000行的巨大代码文件,哪怕是你们团队中最勇敢的程序员也不敢进去。 + +This is great job security for the few who can tame it, but it's hell for the rest of +us. A class that big means even the most seemingly trivial changes can have +far-reaching implications. Soon, the class collects *bugs* faster than it +collects *features*. + +对于能驯服他的少数人这是伟大的工作,但对我们中的其他人是地狱。这么大的类意味着,即使是看似微不足道的变化可以有深远的影响。很快,为类添加*错误*速度明显快于添加*功能*。 + +### The Gordian knot + +### 快刀斩乱麻 + +Even worse than the simple scale problem is the coupling + one. All of the different systems in our game have been tied into a +giant knotted ball of code like: + +比起单纯的规模问题,更糟糕的是耦合。在我们的游戏中所有不同的系统被绑成了一个巨大的代码球。 + +^code gordian + +Any programmer trying to make a change in code like that will need to know +something about physics, graphics, and sound just to make sure they don't break +anything. + +任何试图改变代码的程序员,都需要知道一些关于物理,图形和声音的知识,而这只是为了确保不破坏什么。 + + + +These two problems compound each other; the class touches so many domains that +every programmer will have to work on it, but it's so huge that doing so is a +nightmare. If it gets bad enough, coders will start putting hacks in other parts +of the codebase just to stay out of the hairball that this `Bjorn` class has +become. + +这两个问题彼此混合;这个类涉及如此多的域,每个程序员都得接触它,但它是如此巨大,这就变成了一场噩梦。。如果它变得够糟糕,程序员会黑入代码库的其他部分,仅仅为了躲开这个像毛球一样的`Bjorn`类。 + +### Cutting the knot + +### 抽剑断结 + +We can solve this like Alexander the Great -- with a sword. We'll take our +monolithic `Bjorn` class and slice it into separate parts along domain +boundaries. For example, we'll take all of the code for handling user input and +move it into a separate `InputComponent` class. `Bjorn` will then own an +instance of this component. We'll repeat this process for each of the domains that +`Bjorn` touches. + +我们可以像亚历山大大帝一样解决这个问题——用剑。将`Bjorn`类切片成沿域独立的部分。例如,我们抽出所有处理用户输入的代码,将其移动到一个单独的`InputComponent`类。 `Bjorn`拥有这个部件的一个实例。我们将对于每个`Bjorn`接触域重复这一过程。 + +When we're done, we'll have moved almost everything out of `Bjorn`. All that +remains is a thin shell that binds the components together. We've solved our +huge class problem by simply dividing it up into multiple smaller classes, but +we've accomplished more than just that. + +当我们完成后,我们将几乎所有`Bjorn`的东西都抽走了。剩下的是一个薄壳包着所有的组件。通过将类划分为多个小类,我们已经解决了这个问题。但我们完成了远远不止这些。 + +### Loose ends + +### 宽松的结果 + +Our component classes are now decoupled. Even though `Bjorn` has a +`PhysicsComponent` and a `GraphicsComponent`, the two don't know about each +other. This means the person working on physics can modify their component +without needing to know anything about graphics and vice versa. + +我们的组件类现在解耦了。尽管`Bjorn`有`PhysicsComponent`和`GraphicsComponent`,这两部分都不知道对方的存在。这意味着处理物理的人可以修改组件而不需要了解图形,反之亦然。 + +In practice, the components will need to have *some* interaction between +themselves. For example, the AI component may need to tell the physics component +where Bjørn is trying to go. However, we can restrict this to the +components that *do* need to talk instead of just tossing them all in the same +playpen together. + +在实践中,这些部件需要在他们之间有*一些*相互作用。例如,AI组件可能需要告诉物理组件 +Bjørn试图去哪里。然而,我们可以限制这种交互在*确实*需要交互的组件之间,而不是把他们围在同一个围栏里。 + +### Tying back together + +### 绑到一起 + +Another feature of this design is that the components are now reusable packages. So far, we've +focused on our baker, but let's consider a couple of other kinds of objects in +our game world. *Decorations* are things in the world the player sees but +doesn't interact with: bushes, debris and other visual detail. *Props* are like +decorations but can be touched: boxes, boulders, and trees. *Zones* are the +opposite of decorations -- invisible but interactive. They're useful for things +like triggering a cutscene when Bjørn enters an area. + +这种设计的另一个特性是,所述组件现在是可复用的包。到目前为止,我们专注于面包师,但是让我们考虑几个游戏世界中其他类型的对象。*装饰*是玩家看到但不能交互的事物:灌木,杂物等视觉细。 *道具*像 +*装饰*,但可以交互:箱,巨石,树木。 *区*与装饰相反——无形的,但互动。它们是很好的触发器,比如设计Bjørn进入区域触发过场动画。 + + + +Now, consider how we'd set up an inheritance hierarchy for those classes if we +weren't using components. A first pass might look like: + +现在,考虑如果我们不用组件,我们将如何建立这些类的继承层次。第一遍可能是这样的: + +A class diagram. Zone has collision code and inherits from GameObject. Decoration also inherits from GameObject and has rendering code. Prop inherits from Zone but then has redundant rendering code. + +We have a base `GameObject` class that has common stuff like position and +orientation. `Zone` inherits from that and adds collision detection. Likewise, +`Decoration` inherits from `GameObject` and adds rendering. `Prop` inherits from +`Zone`, so it can reuse the collision code. However, `Prop` can't *also* inherit +from `Decoration` to reuse the *rendering* code without running into the Deadly Diamond. + +我们有基础的`GameObject`类,包含位置和方向之类的通用部分。`Zone`继承它,增加了碰撞检测。同样, +`Decoration`继承`GameObject`,并增加了渲染。 `Prop`继承`Zone`,因此它可以重用碰撞代码。然而,`Prop`不能*同时*继承`Decoration`来重用*渲染*,否则就会造成致命的菱形结构。 + + + +We could flip things around so that `Prop` inherits from `Decoration`, but then +we end up having to duplicate the *collision* code. Either way, there's no clean +way to reuse the collision and rendering code between the classes that need it +without resorting to multiple inheritance. The only other option is to push +everything up into `GameObject`, but then `Zone` is wasting memory on rendering +data it doesn't need and `Decoration` is doing the same with physics. + +我们可以反过来让`Prop`继承`Decoration`,但随后我们最终不得不重复*碰撞*码。无论哪种方式,没有干净方式重用碰撞和渲染代码而不诉诸多重继承。唯一的其他选择是 +一切都继承`GameObject`,但随后`Zone`浪费内存在并不需要的渲染数据上,`Decoration`对物理效果有同样的浪费。 + +Now, let's try it with components. Our subclasses disappear +completely. Instead, we have a single `GameObject` class and two component +classes: `PhysicsComponent` and `GraphicsComponent`. A decoration is simply a +`GameObject` with a `GraphicsComponent` but no `PhysicsComponent`. A zone is the +opposite, and a prop has both components. No code duplication, no multiple +inheritance, and only three classes instead of four. + +现在,让我们尝试用组件。我们的子类彻底消失。相反,我们有一个`GameObject`类和双组分类:`PhysicsComponent`和`GraphicsComponent`。装饰是一个简单的`GameObject`包含`GraphicsComponent`但没有`PhysicsComponent`。一个区恰恰相反,一个道具包含两种组件。没有代码重复,没有多重继承,只有三个类,而不是四个。 + + + +Components are basically plug-and-play for objects. They let us build complex +entities with rich behavior by plugging different reusable component +objects into sockets on the entity. Think software Voltron. + +对对象而言,组件是即插即用的。通过将不同的可重用部件插入对象,他们让我们构建复杂具有丰富的行为实体。就像软件类的战神金刚。 + +## The Pattern + +## 模式 + +A **single entity spans multiple domains**. To keep the domains isolated, the +code for each is placed in its own **component +class**. The entity is reduced to a simple **container of components**. + +**跨越多个域的单一的实体**。为了保持域分离,将每部分的代码放入*各自的组件类*中。实体被简化为一个*组件的容器*。 + + + +## When to Use It + +## 何时使用 + +Components are most commonly found within the core class that defines the +entities in a game, but they may be useful in other places as well. This pattern can +be put to good use when any of these are true: + +组件通常在定义游戏实体的核心部分被使用,但它们在其他地方也会很有用。这个模式在如下情况中可以使用: + + * You have a class that touches multiple domains which you want to keep + decoupled from each other. + + * 有一个涉及了多个域的类,而你想保持这些类互相隔离。 + + * A class is getting massive and hard to work with. + + * 一个类正在变大而且越来越难以使用。 + + * You want to be able to define a variety of objects that share different + capabilities, but using inheritance doesn't let you pick the parts you + want to reuse precisely enough. + + * 你想要能定义一系列分享不同能力的类,但是使用接口不足以让你得到足够的重用部分。 + +## Keep in Mind + +## 记住 + +The Component pattern adds a good bit of complexity over simply making a class and +putting code in it. Each conceptual "object" becomes a cluster of objects that +must be instantiated, initialized, and correctly wired together. Communication +between the different components becomes more challenging, and controlling how +they occupy memory is more complex. + +组件模式比简单的向类中添加代码增加了一点点复杂性。每一个每一个概念上的“对象”要组成真正的对象需要被实例化,被初始化,然后正确的连接到一起。在不同组件中沟通会有些困难,而控制他们如何使用内存就更加复杂。 + +For a large codebase, this complexity may be worth it for the decoupling and +code reuse it enables, but take care to ensure you aren't over-engineering a +"solution" to a non-existent problem before applying this pattern. + +对于大的代码库,为了解耦和重用而付出这样的复杂度是值得的。但是在使用这种模式之前,保证你没有为了一个不存在的问题而“过度设计”。 + +Another consequence of using components is that you often have to hop through a +level of indirection to get anything done. Given the container object, first you +have to get the component you want, *then* you can do what you need. In performance-critical inner loops, this pointer following may +lead to poor performance. + +组件的另一个后果是,你需要多一层跳转才能去做你要去做的事。拿到一个容器对象,先得获得相应的组件,*然后*你猜才能获得你想要的组件。在性能攸关的内部循环中,这种跳转也许会导致糟糕的性能。 + + + +## Sample Code + +## 示例代码 + +One of the biggest challenges for me in writing this book is figuring out how to +isolate each pattern. Many design patterns exist to contain code that itself +isn't part of the pattern. In order to distill the pattern down to its essence, +I try to cut as much of that out as possible, but at some point it becomes a bit +like explaining how to organize a closet without showing any clothes. + +我写这本书的一个最大挑战就是搞明白如何隔离各个模式。许多设计模式包含了并不属于这种模式的代码。为了将模式提取到它的本质表现,我尽可能的消减代码,但是有些时候这就像没有衣服还要说明如何整理衣柜。 + +The Component pattern is a particularly hard one. You can't get a real feel for +it without seeing some code for each of the domains that it decouples, so I'll +have to sketch in a bit more of Bjørn's code than I'd like. The pattern is +really only the component *classes* themselves, but the code in them should +help clarify what the classes are for. It's fake code -- it calls into other +classes that aren't presented here -- but it should give you an idea of what +we're going for. + +组件模式是特别难的一个。如果看不到它解耦的各个域的代码,你就不能获得正确的感觉,因此我会多写一些有关于Bjørn的代码。这个模式事实上只关于将组件变为*类*,但类中的代码可以帮助表明这些类是做什么用的。这是伪代码——他调用了其他不存在的类——但这应该可以给你一个我们正在做什么的概念。 + +### A monolithic class + +### 单块类 + +To get a clearer picture of how this pattern is applied, we'll start by showing +a monolithic `Bjorn` class that does everything we need +but *doesn't* use this pattern: + +为了清晰的看到这个模式是如何应用的,我们需要先展示一个`Bjorn`类,它包含了所有我们需要但是在这个模式中*不需要*的事物的。 + + + +^code 1 + +`Bjorn` has an `update()` method that gets called once per frame by the game: + +`Bjorn`有一个每帧调用的`update()`方法。 + +^code monolithic-update + +It reads the joystick to determine how to accelerate the baker. Then it resolves +its new position with the physics engine. Finally, it draws Bjørn onto the +screen. + +它读取操纵杆以确定如何加速面包师。然后,它用物理引擎解析它的新位置。最后,将Bjørn渲染至屏幕。 + +The sample implementation here is trivially simple. There's no gravity, +animation, or any of the dozens of other details that make a character fun to +play. Even so, we can see that we've got a single function that several +different coders on our team will probably have to spend time in, and it's +starting to get a bit messy. Imagine this scaled up to a thousand lines and you +can get an idea of how painful it can become. + +这里的示例实现是平凡简单。没有重力,动画,或任何让人物有趣的其他细节,。即便如此,我们可以看到,已经有了一个不同的程序员将花费时间于其上的单一功能函数,而且它开始变得有点混乱。想象一下放大到一千行,你就知道这会有多难受了。 + +### Splitting out a domain + +### 分离领域 + +Starting with one domain, let's pull a piece out of `Bjorn` and push it into a +separate component class. We'll start with the first domain that gets processed: +input. The first thing `Bjorn` does is read in user input and adjust his +velocity based on it. Let's move that logic out into a separate class: + +从一个域开始,让我们从`Bjorn`去除一部分将它归入一个分离的组件类。我们从第一个开始执行的域开始:输入。`Bjorn`做的第一件事就是读取玩家的输入,然后基于此调整他的速度。让我们将这部分逻辑移入一个分离的类: + +^code 2 + +Pretty simple. We've taken the first section of `Bjorn`’s `update()` +method and put it into this class. The changes to `Bjorn` are also +straightforward: + +很简答把吧。我们将`Bjorn`的`update()`取出,将其放入这个类。对`Bjorn`的改变也很直接: + +^code 3 + +`Bjorn` now owns an `InputComponent` object. Where before he was handling user +input directly in the `update()` method, now he delegates to the component: + +`Bjorn`现在拥有了一个`InputComponent`对象。之前它在`update()`方法中直接处理用户输入,现在他用组件代替: + +^code 4 + +We've only started, but already we've gotten rid of some coupling -- the main +`Bjorn` class no longer has any reference to `Controller`. This will come in +handy later. + +我们刚刚开始,但我们已经摆脱了一些耦合——主要的`Bjorn`现在已经与`Controller`无关了。这会派上用场的。 + +### Splitting out the rest + +### 将剩下的分割出来 + +Now, let's go ahead and do the same cut-and-paste job on the physics and graphics +code. Here's our new `PhysicsComponent`: + +现在让我们对物理和图像代码继续这种剪切粘贴的工作。这是我们新的 `PhysicsComponent`: + +^code 5 + +In addition to moving the physics *behavior* out of the main `Bjorn` class, you +can see we've also moved out the *data* too: The `Volume` object is now owned by +the component. + +为了将物理*行为*移出`Bjorn`类,你可以看到我们也移出了*数据*:`Volume`对象已经是部件的一部分了。 + +Last but not least, here's where the rendering code lives now: + +最后,这是现在的渲染代码: + +^code 6 + +We've yanked almost everything out, so what's left of our humble pastry chef? +Not much: + +我们几乎将所有的东西都移出来了,所以我们谦虚的面包师还剩下什么?没什么了: + +^code 7 + +The `Bjorn` class now basically does two things: it holds the set of components +that actually define it, and it holds the state that is shared across multiple +domains. Position and velocity are still in the core `Bjorn` class for two +reasons. First, they are "pan-domain" state -- almost every component will make +use of them, so it isn't clear which component *should* own them if we did want +to push them down. + +`Bjorn`类现在基本呢上就做两件事:他拥有定义它的组件,以及在不同域之间要分享的数据。有两个原因导致位置和速度仍然在`Bjorn`的核心类中:首先,他们是“泛领域”状态——几乎每个组件都需要使用他们,所以我们想要提取出来的时候,哪个组件*应该*拥有他们并不明确。 + +Secondly, and more importantly, it gives us an easy way for the components to +communicate without being coupled to each other. Let's see if we can put that to +use. + +第二,而且更重要的是,他给了我们无需让组件耦合就能沟通的简易方法。让我们看看我们能不能使用这一点。 + +### Robo-Bjørn + +### Robo-Bjørn + +So far, we've pushed our behavior out to separate component classes, but we +haven't *abstracted* the behavior out. `Bjorn` still knows the exact concrete +classes where his behavior is defined. Let's change that. + +到目前为止,我们将我们的行为归入了不同的组件类,但哦我们还没有将行为*抽象*出来。`Bjorn`还是知道每个类的具体定义的行为。让我们改变这一点。 + +We'll take our component for handling user input and hide it behind an +interface. We'll turn `InputComponent` into an abstract base class: + +我们取出处理输入的部件,将其藏在接口之后。我们将`InputComponent`变为一个抽象基类。 + +^code 8 + +Then, we'll take our existing user input handling code and push it down into a +class that implements that interface: + +然后,我们将现有的处理输入的代码取出,放进一个实现接口的类中。 + +^code 9 + +We'll change `Bjorn` to hold a pointer to the input component instead of having +an inline instance: + +我们将`Bjorn`改为只拥有一个指向输入组件的指针,而不是有一个内联的实例。 + +^code 10 + +Now, when we instantiate `Bjorn`, we can pass in an input component for it to +use, like so: + +现在但我们实例化`Bjorn`,我们可以传入一个输入组件使用,就像下面这样: + +^code 11 + +This instance can be any concrete type that implements our abstract +`InputComponent` interface. We pay a price for this -- `update()` is now a virtual +method call, which is a little slower. What do we get in return for this cost? + +这个实例可以是任何实现了抽象`InputComponent`接口的类型。我们为此付出了代价——`update()`现在是一个虚方法调用了,这会慢一些。这一代价的回报是什么? + +Most consoles require a game to support "demo mode." If the player sits at the +main menu without doing anything, the game will start playing automatically, +with the computer standing in for the player. This keeps the game from burning +the main menu into your TV and also makes the game look nicer when it's running +on a kiosk in a store. + +大多数的主机需要游戏支持“演示模式”。如果玩家停在主菜单没有做任何事情,游戏就会自动开始自动运行,等待电脑接入一个玩家。这让你电视上的主菜单看上去更有生机,同时也是在商店中很好的展示。 + +Hiding the input component class behind an interface lets us get that working. +We already have our concrete `PlayerInputComponent` that's normally used when +playing the game. Now, let's make another one: + +隐藏在输入组件后的类帮我们实现了这一点,我们已经有了具体的`PlayerInputComponent`,供我们在玩游戏时使用。现在让我们完成另一个: + +^code 12 + +When the game goes into demo mode, instead of constructing Bjørn like we did +earlier, we'll wire him up with our new component: + +当游戏进入演示模式,不像我们之前演示的那样构造Bjørn,我们将它和一个新组件连接起来: + +^code 13 + +And now, just by swapping out a component, we've got a fully functioning +computer-controlled player for demo mode. We're able to reuse all of the other +code for Bjørn -- physics and graphics don't even know there's a difference. +Maybe I'm a bit strange, but it's stuff like this that gets me up in the morning. + +现在,只需要更改组件,我们有了为演示模式而设计的电脑控制的玩家。我们可以重用所有Bjørn的代码——物理和图像都不知道这里有了变化。也许我有些奇怪,但这就是每天能让我从起床的事物。 + + + +### No Bjørn at all? + +### 完全没有Bjørn? + +If you look at our `Bjorn` class now, you'll notice there's nothing really +"Bjørn" about it -- it's just a component bag. In fact, it looks like a pretty +good candidate for a base "game object" class that we can use for *every* object +in the game. All we need to do is pass in *all* the components, and we can build +any kind of object by picking and choosing parts like Dr. Frankenstein. + +如果你现在看看我们的`Bjørn`类,你会意识到那里完全没有“Bjørn”——那只是一个组件包。事实上,它是*每个*游戏中的对象都能继承的“游戏对象”基类的一个好后溴铵人。我们可以像佛兰肯斯坦一样通过挑选拼装部件构建任何对象。 + +Let's take our two remaining concrete components -- physics and graphics -- and +hide them behind interfaces like we did with input: + +让我们将剩下的两个具体组件——物理和图像——像我们对输入那样藏到接口之后。 + +^code 14 + +Then we re-christen `Bjorn` into a generic `GameObject` +class that uses those interfaces: + +然后我们将`Bjørn`改为一个使用这些接口的通用`GameObject`类。 + +^code 15 + + + +Our existing concrete classes will get renamed and implement those interfaces: + +我们现有的具体类将被重命名并实现这些接口。 + +^code 16 + +And now we can build an object that has all of Bjørn's original behavior without +having to actually create a class for him, just like this: + +现在我们无需为Bjørn建立具体类,就能构建拥有所有Bjørn的行为的对象。 + + + +^code 17 + + + +By defining other functions that instantiate `GameObjects` with different +components, we can create all of the different kinds of objects our game needs. + +通过用不同组件实例化`GameObject`,我们可以构建游戏需要的任何组件。 + +## Design Decisions + +## 设计决策 + +The most important design question you'll need to answer with this pattern is, +"What set of components do I need?" The answer there is going to depend on the +needs and genre of your game. The bigger and more complex your engine is, the +more finely you'll likely want to slice your components. + +这章中你最需要回答的设计问题是“我需要什么样的组件?”回答取决于你游戏的需求和风格。你的引擎越大越复杂,你就越想将它们划分成组件。 + +Beyond that, there are a couple of more specific options to consider: + +除此之外,还有几个更具体的选项要回答: + +### How does the object get its components? + +### 对象如何获取组件? + +Once we've split up our monolithic object into a few separate component parts, +we have to decide who puts the parts back together. + +我们一旦将单块对象分割为多个分离的组件部分,我们就需要决定谁将它们拼到一起。 + + * **If the object creates its own components:** + + * *如果对象创建它的组件:* + + * *It ensures that the object always has the components it needs.* You + never have to worry about someone forgetting to wire up the right + components to the object and breaking the game. The container object + itself takes care of it for you. + + * *这保证了对象总是能拿到他需要的组件。*你永远不必担心某人忘记连接正确的组件然后破坏了整个游戏。容器类自己会处理这个问题。 + + * *It's harder to reconfigure the object.* One of the powerful features of + this pattern is that it lets you build new kinds of objects simply by + recombining components. If our object always wires itself with the same + set of hard-coded components, we aren't taking advantage of that + flexibility. + + * *重新设置对象比较困难*这个模式的一个强力特性就是让你只需要重新组合组件就可以创建新的对象。如果对象总是自己用硬编码的组件组装自己,我们就无法使用这个灵活度。 + + * **If outside code provides the components:** + + * *如果外部代码提供组件:* + + * *The object becomes more flexible.* We can completely change the + behavior of the object by giving it different components to work with. + Taken to its fullest extent, our object becomes a generic component + container that we can reuse over and over again for different purposes. + + * *对象更加灵活。*我们可以改变不同的组件,这样就能改变对象的行为。通过通用组件,我们的对象变成了一个组件容器,我们可以一遍又一遍的为不同目的重用它。 + + * *The object can be decoupled from the concrete component types.* If + we're allowing outside code to pass in components, odds are good that + we're also letting it pass in *derived* component types. At that point, + the object only knows about the component *interfaces* and not the + concrete types themselves. This can make for a nicely encapsulated + architecture. + + * *对象可以与确定的组件类型解耦。*乳沟我们允许外部代码提供组件,好处是我们也可以传递*派生*的组件类型。这样,对象只知道组件的*接口*而不知道组件的具体类型。这是一个很好的封装结构。 + +### How do components communicate with each other? + +### 组件之间如何通信? + +Perfectly decoupled components that function in isolation is a nice ideal, but +it doesn't really work in practice. The fact that these components are part of the +*same* object implies that they are part of a larger whole and need to +coordinate. That means communication. + +完美解耦的组件不需要考虑这个问题,但在真正的实践中行不通。事实上组件属于*同一个*对象暗示了他们属于更大整体的一部分,需要相互协同。这就意味着通信。 + +So how can the components talk to each other? There are a couple of options, but +unlike most design "alternatives" in this book, these aren't exclusive -- you will +likely support more than one at the same time in your designs. + +所以组件如何相互通信呢?这里有很多选项,但不像这本书中其他的“选项”,他们并不冲突——你可能在一个设计中支持多种方案。 + + * **By modifying the container object's state:** + + * *通过修改容器对象的状态:* + + * *It keeps the components decoupled.* When our `InputComponent` set + Bjørn's velocity and the `PhysicsComponent` later used it, the two + components had no idea that the other even existed. For all they knew, + Bjørn's velocity could have changed through black magic. + + * *这让组件解耦。*当我们的`InputComponent`设置了Bjørn的速度,而后`PhysicsComponent`使用它,这两个组件都不知道对方的存在。他们的理解中,Bjørn的速度是被黑魔法改变的。 + + * *It requires any information that components need to share to get pushed + up into the container object.* Often, there's state that's really only + needed by a subset of the components. For example, an animation and a + rendering component may need to share information that's + graphics-specific. Pushing that information up into the container object + where *every* component can get to it muddies the object class. + + * *他需要将更多组件需要分享的数据存储在容器类中。*通常状态只有几个组件需要共享。比如,一个动画组件和一个渲染组件需要共享图形专用的信息。将信息存入容器类会让*所有*组件都能获得这样的信息。 + + Worse, if we use the same container object class with different + component configurations, we can end up wasting memory on state that + isn't needed by *any* of the object's components. If we push some + rendering-specific data into the container object, any invisible object + will be burning memory on it with no benefit. + + 更糟的是,如果我们用不同组件设置构建的相同容器类,我们最终会浪费内存存储没有*任何*对象组件需要的状态。如果我们将渲染专用的数据放入容器对象中,任何隐形对象都会无益的消耗内存。 + + * *It makes communication implicit and dependent on the order that + components are processed.* In our sample code, the original monolithic + `update()` method had a very carefully laid out order of operations. The + user input modified the velocity, which was then used by the physics + code to modify the position, which in turn was used by the rendering + code to draw Bjørn at the right spot. When we split that code out into + components, we were careful to preserve that order of operations. + + * *这让组件的通信基于组件运行的顺序。*在同样 的代码中原先一整块的`update()`代码小心的排列这些笑傲做。玩家的输入修改了速度,速度被物理代码使用修改位置,位置被渲染代码使用将Bjørn画到该有的地方。当我们将这些代码划入组件时,我们还是得小心翼翼的保持这种操作顺序。 + + If we hadn't, we would have introduced subtle, + hard-to-track bugs. For example, if we'd updated the graphics component + *first*, we would wrongly render Bjørn at his position on the *last* + frame, not this one. If you imagine several more components and lots + more code, then you can get an idea of how hard it can be to avoid bugs + like this. + + 如果我们没有,我们就引入了微妙而难以追踪的漏洞。比如,我们*先*更新图形组件,我们就错误的将Bjørn渲染再来他*上一帧*所处的位置上,而不是这一帧的。如果你考虑更多的组件和更多的代码,那你就可以想象要避免这样的漏洞有多么困难了。 + + + + * **By referring directly to each other:** + + * *通过他们之间相互引用:* + + The idea here is that components that need to talk will have direct + references to each other without having to go through the container + object at all. + + 这里的想法是组件有他们需要交流组件的引用,这样他们就直接交流,无需通过容器类。 + + Let's say we want to let Bjørn jump. The graphics code needs to know if + he should be drawn using a jump sprite or not. It can determine this by + asking the physics engine if he's currently on the ground. An easy way + to do this is by letting the graphics component know about the physics + component directly: + + 假设哦我们想让Bjørn跳跃。图像代码想要知道他需要使用一个单独的跳跃图像还是不用。这可以通过询问物理引擎它现在在不在地上来确定。一种简单的方式是图像组件直接指导物理组件的存在: + + ^code 18 + + When we construct Bjørn's `GraphicsComponent`, we'll give it a reference + to his corresponding `PhysicsComponent`. + + 但我们构建Bjørn的`GraphicsComponent`时,我们给他一个对应的`PhysicsComponent`引用。 + + * *It's simple and fast.* Communication is a direct method call from one + object to another. The component can call any method that is supported + by the component it has a reference to. It's a free-for-all. + + * *简单快捷。*通信是一个对象到另一个的直接方法调用。组件可以调用任何一个引用对象的方法。什么都可以。 + + * *The two components are tightly coupled.* The downside of the + free-for-all. We've basically taken a step back towards our monolithic + class. It's not quite as bad as the original single class though, since + we're at least restricting the coupling to only the component pairs that + need to interact. + + * *两个组件紧紧绑在了一起。*什么都可以的坏处。我们向使用一整块类又退回来一步。这比只用单一类好一点,至少我们现在只是把需要通信的类绑在了一起。 + + * **By sending messages:** + + * *通过发送消息:* + + * This is the most complex alternative. We can actually build a little + messaging system into our container object and let the components + broadcast information to each other. + + * 这是最复杂的选项。我们可以在我们的容器类中建一个小小的消息系统,允许组件相互发送消息。 + + Here's one possible implementation. We'll start by defining a base + `Component` interface that all of our components will implement: + + 这是一种可能的实现。我们从每个组件都会实现的`Component`接口开始: + + ^code 19 + + It has a single `receive()` method that component classes implement in + order to listen to an incoming message. Here, we're just using an `int` + to identify the message, but a fuller implementation could attach + additional data to the message. + + 它有一个简单的`receive()`方法,每一个需要接受消息的类都要实现它。这里,我们使用一个`int`来定义消息。但更全面的消息实现应该可以引用附加的消息。 + + Then, we'll add a method to our container object for sending messages: + + 然后,我们向容器类添加发送消息的方法。 + + ^code 20 + + Now, if a component has access to its + container, it can send messages to the container, which will + rebroadcast the message to all of the contained components. (That + includes the original component that sent the message; be careful that + you don't get stuck in a feedback loop!) This has a couple of + consequences: + + 现在,如果组件能够接触容器,他就能向容器发送消息,浙江向所有的组件广播。(包括了原先发送消息的组件,小心别陷入消息的无限循环中!)这会有一些结果: + + + + * *Sibling components are decoupled.* By going through the parent container object, like our + shared state alternative, we ensure that the components are still + decoupled from each other. With this system, the only coupling they have + is the message values themselves. + + * *同级组件解耦*通过父级容器对象,就像我们共享状态的方案一样,我们保证了组件之间仍然是解耦的。使用了这套系统,组件之间唯一的耦合是他们发送的消息值。 + + + + * *The container object is simple.* Unlike using shared state where the + container object itself owns and knows about data used by the + components, here, all it does is blindly pass the messages along. That + can be useful for letting two components pass very domain-specific + information between themselves without having that bleed into the + container object. + + * *容器类很简单。*不像使用共享状态那样,容器类自己无需知道组件使用了什么数据,它只是将消息发送出去。这可以让组件发送领域特有的数据而无需打扰容器对象。 + +Unsurprisingly, there's no one best answer here. What you'll likely end up doing +is using a bit of all of them. Shared state is useful for the really basic stuff +that you can take for granted that every object has -- things like position and +size. + +不出意料的,这里没有最好的回答。这些你最终可能都会使用一些。共享状态对于每一个对象都有的数据是很好用的——比如位置和大小。 + +Some domains are distinct but still closely related. Think animation and +rendering, user input and AI, or physics and collision. If you have separate +components for each half of those pairs, you may find it easiest to just let +them know directly about their other half. + +有些领域不同却仍然紧密相关。想想动画和渲染,用户输入和AI,或者物理和粒子。如果你有这样一对分离的组件,你会发现直接拥有相互引用也许更加容易。 + +Messaging is useful for "less important" communication. Its fire-and-forget +nature is a good fit for things like having an audio component play a sound when +a physics component sends a message that the object has collided with something. + +消息对于“不那么重要”的通信很有用。发送后不管的特性对于物理组件发现事物碰撞后发送消息,让音乐组件播放声音这种事情是很有效的。 + +As always, I recommend you start simple and then add in additional +communication paths if you need them. + +就像以前一样,我建议你从简单的开始,然后如果需要的话,加入其他的通信路径。 + +## See Also + +## 参见 + + * The [Unity](http://unity3d.com) framework's core [`GameObject`](http://docs.unity3d.com/Documentation/Manual/GameObjects.html) class is designed + entirely around [components](http://docs.unity3d.com/Manual/UsingComponents.html). + + * [Unity](http://unity3d.com)核心架构中[`GameObject`](http://docs.unity3d.com/Documentation/Manual/GameObjects.html)类完全根据这样的原则设计 [components](http://docs.unity3d.com/Manual/UsingComponents.html)。 + + * The open source [Delta3D](http://www.delta3d.org) engine has a base + `GameActor` class that implements this pattern with the appropriately named + `ActorComponent` base class. + + * 开源的[Delta3D](http://www.delta3d.org)引擎有一个`GameActor`基类通过`ActorComponent`基类实现了这种模式。 + + * Microsoft's XNA game framework + comes with a core `Game` class. It owns a collection of `GameComponent` + objects. Where our example uses components at the individual game entity + level, XNA implements the pattern at the level of the main game object + itself, but the purpose is the same. + + * 微软的XNA游戏框架有一个核心的`Game`类。他拥有一系列`GameComponent`对象。我们在游戏实体层使用组件,XNA在游戏主对象上实现了这种模式,但意图是一样的。 + + * This pattern bears resemblance to the Gang of Four's Strategy pattern. Both + patterns are about taking part of an object's behavior and delegating it to + a separate subordinate object. The difference is that with the Strategy + pattern, the separate "strategy" object is usually stateless -- it + encapsulates an algorithm, but no data. It defines *how* an object behaves, + but not *what* it is. + + 这种模式与GoF的策略模式 类似。两种模式都是将对象的行为取出划入一个单独的重述对象。与对象模式不同的是,分离的策略模式通常是无状态的——它分装了算法,而没有数据。他定义了对象*如何*行动,但没有定义对象*是*什么。 + + Components are a bit more self-important. They often hold state that + describes the object and helps define its actual identity. However, the line + may blur. You may have some components that don't need any local state. In + that case, you're free to use the same component *instance* across multiple + container objects. At that point, it really is behaving more akin to a + strategy. + + 组件本身更加重要。他们经常保存了描述对象的状态,这有助于确定其真正的身份。但是,这条线很模糊。你也许有一些组件根本没有任何状态。在这种情况下,你可以在不同的容器对象中使用相同的组件*实例*。这样看来,他的行为看上去确实更像一种策略。 diff --git a/book/data-locality.markdown b/book/data-locality.markdown new file mode 100644 index 0000000..6c8e4af --- /dev/null +++ b/book/data-locality.markdown @@ -0,0 +1,1260 @@ +^title Data Locality +^section Optimization Patterns + +数据局部性 +优化模式 + +## Intent + +## 意图 + +*Accelerate memory access by arranging data to take advantage of CPU caching.* + +*安排数据充分使用CPU的缓存来加速内存读取。* + +## Motivation + +## 动机 + +We've been lied to. They keep showing us charts where CPU speed goes up and up +every year as if Moore's Law isn't just a historical observation but some kind +of divine right. Without lifting a finger, we software folks watch our programs +magically accelerate just by virtue of new hardware. + +我们被欺骗流。他们一直向我们展示CPU速度每年递增的图表,就好象摩尔定律不是一个观察历史的结果,而是某种定理。无需吹灰之力,我们的软件凭借着新硬件就可以奇迹般的加速。 + +Chips *have* been getting faster (though even that's plateauing now), but the +hardware heads failed to mention something. Sure, we can *process* data faster +than ever, but we can't *get* that data faster. + +芯片*确实*爱越来越快(就算现在也在变快),但硬件头头没有提到一些事情。是的,我们可以更快的*处理*数据,但我们不能更快的*获得*数据。 + + + +A chart showing processor and RAM speed from 1980 to 2010. Processor speed increases quickly, but RAM speed lags behind. + + + +For your super-fast CPU to blow through a ream of calculations, it actually has +to get the data out of main memory and into registers. As you can see, RAM hasn't +been keeping up with increasing CPU speeds. Not even close. + +为了让你超高速的CPU可以刮起指令风暴,它需要从内存获取数据加载到寄存器。如你所知,RAM没有紧跟CPU的速度增长,差远了。 + +With today's hardware, it can take *hundreds* of cycles to fetch a byte of data +from RAM. If most instructions need data, and it takes +hundreds of cycles to get it, how is it that our CPUs aren't sitting idle 99% +of the time waiting for data? + +借助现代的硬件,需要*上百个*周期从RAM获得一比特的数据。如果大部分指令需要的数据都需要上百个周期去获取,那么为什么我们的CPU没有在99%的时间中等待数据而空转? + +Actually, they *are* stuck waiting on memory an astonishingly large fraction of +time these days, but it's not as bad as it could be. To explain how, let's take +a trip to the Land of Overly Long Analogies... + +事实上,停滞等待内存*确实*消耗很长时间,但是没有那么糟糕。为了解释为什么,让我们看一看这一长串类比…… + + + +### A data warehouse + +### 数据仓库 + +Imagine you're an accountant in a tiny little office. Your job is to request a +box of papers and then do some accountant-y stuff +with them -- add up a bunch of numbers or something. You must do this for +specific labeled boxes according to some arcane logic that only makes sense to +other accountants. + +想象一下,你是一个小办公室里的会计。你的任务是拿一盒文件,然后做一些会计工作——把数据加起来什么的。你必须通过一堆只对其他会计师有意义的晦涩难懂的逻辑取出特定标记的盒子而工作。 + + + +Thanks to a mixture of hard work, natural aptitude, and stimulants, you can +finish an entire box in, say, a minute. There's a little problem, though. All of +those boxes are stored in a warehouse in a separate building. To get a box, you +have to ask the warehouse guy to bring it to you. He goes and gets a forklift +and drives around the aisles until he finds the box you want. + +感谢辛勤的工作,天生的悟性,还有兴奋剂,你可以在一分钟内完成一个盒子。但是这里有一个小小的问题。所有这些盒子都存储在一个分离的仓库中。想要拿到一个盒子,你需要让仓库管理员带给你。他开着叉车在传送带周围移动,直到他能找到你要的盒子。 + +It takes him, seriously, an entire day to do this. Unlike you, he's not getting +employee of the month any time soon. This means that no matter how fast you are, +you only get one box a day. The rest of the time, you just sit there and +question the life decisions that led to this soul-sucking job. + +这会消耗他,认真的说,一整天才能完成。不像你,他下个月就不会被雇佣了。这就意味着无论你有多快,你一天只能拿到一个盒子。剩下的时间,你只能坐在那里,质疑这个折磨灵魂的工作。 + +One day, a group of industrial designers shows up. Their job is to improve the +efficiency of operations -- things like making assembly lines go faster. After +watching you work for a few days, they notice a few things: + +一天,一组工业设计师出现了。他们的人物是提高操作的效率——比如让传送带跑得更快。在看着你工作流几天后,他们发现了几件事情: + + * Pretty often, when you're done with one box, the next box you request is + right next to it on the same shelf in the + warehouse. + + * 很经常的,当你处理一个盒子的时候,下一个你需要的盒子就在仓库同样架子上面。 + + * Using a forklift to carry a single box of papers is pretty dumb. + + * 叉车只取一个盒子很笨。 + + * There's actually a little bit of spare room in the corner of your office. + + * 在你的办公室角落里还是有些空余空间的。 + + + +They come up with a clever fix. Whenever you request a box from the warehouse +guy, he'll grab an entire pallet of them. He gets the box you want and then some +more boxes that are next to it. He doesn't know if you want those (and, given +his work ethic, clearly doesn't care); he simply takes as many as he can fit on +the pallet. + +他们想出来一个巧妙的办法。无论何时你问仓库要一个盒子的时候,他都会将一托盘盒子带个你。他给你想要的盒子,以及它周围的盒子。他不知道你是不是想要这些(而且,根据他的工作条件,他根本不在乎);他只是尽可能的塞满托盘。 + +He loads the whole pallet and brings it to you. Disregarding concerns for +workplace safety, he drives the forklift right in and drops the pallet in the +corner of your office. + +他装满托盘然后带给你。无视工作场地的安全,他直接将叉车开到你的办公室然后将托盘放在你办公室的角落。 + +When you need a new box, now, the first thing you do is see if it's already on +the pallet in your office. If it is, great! It only takes you a second to grab +it and get back to crunching numbers. If a pallet holds fifty boxes and you +got lucky and *all* of the boxes you need happen to be on it, you can churn +through fifty times more work than you could before. + +当你需要一个新盒子,你需要做的第一件事就是看看它是不是在你办公室角落的托盘里。如果在,很好!你只需要几分钟拿起它然后继续计算数据。如果一个托盘中有乌五十个盒子,而你幸运的需要*所有*盒子,你可以以以前五十倍的速度工作。 + +But if you need a box that's *not* on the pallet, you're back to square one. +Since you can only fit one pallet in your office, your warehouse friend will +have to take that one back and then bring you an entirely new one. + +但是如果你需要的盒子*不在*托盘上,你就要一新托盘。由于你的办公室里只能放一个托盘,你的仓库朋友只能将老的拿走,带给你一个全新的。 + +### A pallet for your CPU + +### 你CPU的托盘 + +Strangely enough, this is similar to how CPUs in modern computers work. In case +it isn't obvious, you play the role of the CPU. Your desk is the CPU's +registers, and the box of papers is the data you can fit in them. The warehouse +is your machine's RAM, and that annoying warehouse guy is the bus that pulls +data from main memory into registers. + +奇怪的是,这就是现代CPU运转的方式。如果还是不够明显,你是CPU的角色。你的桌子是CPU的寄存器,一盒文件就是你桌上可以放下的数据。仓库是机器的RAM,那个烦人的仓库管理员是从主存加载数据到寄存器的总线。 + +If I were writing this chapter thirty years ago, the analogy would stop there. +But as chips got faster and RAM, well, *didn't*, hardware engineers started +looking for solutions. What they came up with was *CPU caching*. + +如果我在三十年前写这一章,这个比喻就到此为止了。但是芯片越来越快,而RAM,好吧,“没有跟上”,硬件工程师开始寻找解决方案。他们想到的是*CPU缓存*。 + +Modern computers have a little chunk of memory right +inside the chip. The CPU can pull data from this much faster than it can from +main memory. It's small because it has to fit in the chip and because the faster +type of memory it uses (static RAM or "SRAM") is way more expensive. + +现代的电脑在芯片内部有一小块存储器。CPU从那上面取数据比从内存取数据快得多。它很小,因为它需要放在芯片上,它很快,因为它使用的(静态RAM,或者SRAM)内存更贵。 + + + +This little chunk of memory is called a *cache* (in particular, the chunk on the +chip is your *L1 cache*), and in my belabored analogy, its part was played by the +pallet of boxes. Whenever your chip needs a byte of data from RAM, it +automatically grabs a whole chunk of contiguous memory -- usually around 64 to +128 bytes -- and puts it in the cache. This dollop of memory is called a *cache +line*. + +这一小片内存被称为*缓存*(特别的,芯片上的被称为*L1级缓存*),在我的比喻中,这一块是由托盘扮演的。无论何时你的芯片需要从RAM取一字节的数据,他自动将一整块内存读入——通常是64到128字节——然后将其放入缓存。这些一次性传输的字节被称为*cache line*。 + +A cache line showing the one byte requested along with the adjacent bytes that also get loaded into the cache. + +If the next byte of data you need happens to be in +that chunk, the CPU reads it straight from the cache, which is *much* faster +than hitting RAM. Successfully finding a piece of data in the cache is called a +*cache hit*. If it can't find it in there and has to go to main memory, that's a +*cache miss*. + +如果你需要的下一个字节就在这块上,CPU从缓存中直接读取,比从RAM中读取*快得多*。成功从缓存中找到数据被称为“缓存命中”。如果不能从中获得而得去主存里取,这就是一次*缓存不命中*。 + + + +When a cache miss occurs, the CPU *stalls* -- it can't process the next +instruction because it needs data. It sits there, bored out of its mind for a +few hundred cycles until the fetch completes. Our mission is to avoid that. +Imagine you're trying to optimize some performance-critical piece of game code +and it looks like this: + +当缓存不命中时,CPU*空转*——它不能执行下一条指令,因为它没有数据。它坐在那里,无聊的等待几百个周期直到数据被获取了。我们的任务是避免这一点。想象你在优化一块性能攸关的游戏代码,长得像这样: + +^code do-nothing + +What's the first change you're going to make to that code? Right. Take out that +pointless, expensive function call. That call is equivalent to the performance +cost of a cache miss. Every time you bounce to main memory, it's like you put a +delay in your code. + +你会做的第一个变化是什么?对了。将那个无用的,代价巨大的函数调用拿出来。这个调用等价于一次缓存不命中的代价。每一次你跳到内存,你都会延误你的代码。 + +### Wait, data is performance? + +### 等等,数据是性能? + +When I started working on this chapter, I spent some time putting together +little game-like programs that would trigger best case and worst case cache +usage. I wanted benchmarks that would thrash the cache so I could see first-hand +how much bloodshed it causes. + +当我开始写这一章的时候,我花费了一些时间收集缓存使用最好和最坏的例子。我想要缓存速度的基准,这样我可以得到浪费的第一手消息。 + +When I got some stuff working, I was surprised. I knew it was a big deal, but +there's nothing quite like seeing it with your own eyes. I +wrote two programs that did the *exact same* computation. The only +difference was how many cache misses they caused. The slow one was *fifty times* +slower than the other. + +但我得到了一些工作的例子,我惊到了。我知道这是一个大问题,但眼见为实。我写过两个程序完成*完全相同*的计算。唯一的区别是它们会造成缓存不命中的数量。慢的那个比另一个慢*五十*倍。 + + + +This was a real eye-opener to me. I'm used to thinking of performance being an +aspect of *code*, not *data*. A byte isn't slow or fast, it's just some static +thing sitting there. But because of caching, *the way you organize data +directly impacts performance*. + +这让我大开眼界。我一直从*代码*的角度考虑性能,而不是*数据*。一个字节没有射门快慢,它是静态的。但是因为缓存的存在,*你组织数据的方式直接影响了性能*。 + +The challenge now is to wrap that up into something that fits into a chapter +here. Optimization for cache usage is a huge topic. I haven't even touched on +*instruction caching*. Remember, code is in memory too and has to be loaded onto +the CPU before it can be executed. Someone more versed on the subject could +write an entire book on it. + +现在真正的挑战是将其打包成在一章内可以讲完的东西。优化缓存使用是一个很大的话题。我还没有谈到*指令缓存*呢。记住,代码也在内存上,而且在执行前需要加载到CPU上。有些更熟悉这个主题的人可以就这个问题写一整本书。 + + + +Since you're already reading *this* book right now, though, I have a few basic +techniques that will get you started along the path of thinking about how data +structures impact your performance. + +既然你已经在阅读*这本书*了,我有几个基本技术让你开始考虑数据结构是如何影响性能的。 + +It all boils down to something pretty simple: whenever the chip reads some +memory, it gets a whole cache line. The more you can use stuff in that cache line, the faster you go. So the goal then is to +*organize your data structures so that the things you're processing are next to +each other in memory*. + +这可以归结得很简单:芯片读内存,总是获得一整块cache line,你能从cache line读到越多你要的东西,你跑的就越快。所以目标是*组织数据结构,让你要处理的数据紧紧相邻。* + + + +In other words, if your code is crunching on `Thing`, then `Another`, then +`Also`, you want them laid out in memory like this: + +换言之,如果你的正在处理`Thing`,然后`Another`然后`Also`,,你需要它们这样呆在内存里: + +Thing, Another, and Also laid out directly next to each other in order in memory. + +Note, these aren't *pointers* to `Thing`, `Another`, and `Also`. This is the +actual data for them, in place, lined up one after the other. As soon as the CPU +reads in `Thing`, it will start to get `Another` and `Also` too (depending on +how big they are and how big a cache line is). When you start working on them +next, they'll already be cached. Your chip is happy, and you're happy. + +注意,这不是`Thing`,`Another`,和`Also`的*指针*。这就是真实的数据,一个接着一个。CPU一读到`Thing`,他也会读取`Another`和`Also`(取决于数据有多大和cache line 有多大)。当你开始下一个的时候,他们已经在缓存上了。你的芯片很高兴,你也很高兴。 + +## The Pattern + +## 模式 + +Modern CPUs have **caches to speed up memory access**. These can access memory +**adjacent to recently accessed memory much quicker**. Take advantage of that to +improve performance by **increasing data locality** -- keeping data in +**contiguous memory in the order that you process it**. + +现代的CPU有*缓存来加速内存读取*。它可以*更快的读取最近访问过的内存毗邻的内存*。通过*提高内存局部性*来提高性能——保证数据以*你处理它们的连续内存上*。 + +## When to Use It + +## 何时使用 + +Like most optimizations, the first guideline for using the Data Locality pattern is *when you have a +performance problem.* Don't waste time applying this to some infrequently +executed corner of your codebase. Optimizing code that doesn't need it just +makes your life harder since the result is almost always more complex and less +flexible. + +就像很多其他的优化方案一样,使用数据局部性的第一条准则是*在你遇到性能问题的时候使用。*不要将其应用在你代码库不经常使用的角落上。优化代码不会让你过得更轻松,因为其结果往往更加复杂,更加缺乏灵活性。 + +With this pattern specifically, you'll also want to be sure your performance +problems *are caused by cache misses*. If your code is slow for other reasons, +this won't help. + +就本模式而言,你还得确认你的性能问题*确实由缓存不命中*引发。如果你的代码是因为其他原因而缓慢,这个模式不会有帮助。 + +The cheap way to profile is to manually add a bit of instrumentation that checks +how much time has elapsed between two points in the code, hopefully using a +precise timer. To catch poor cache usage, you'll want something a little more +sophisticated. You really want to see how many cache misses are occurring and +where. + +简单的检查方案是手动添加一些指令,检查代码中任意两点间消耗的时间,希望能使用精确的计时器。为了找到糟糕的缓存使用,你需要使用更加复杂的东西。你想要知道缓存不命中有多少发生,又是在哪里发生的。 + +Fortunately, there are profilers out there that +report this. It's worth spending the time to get one of these working and make +sure you understand the (surprisingly complex) numbers it throws at you before +you do major surgery on your data structures. + +幸运的是,这里有工具报告这些。在你在数据结构上做大手术前,花一些时间了解他们是如何工作的,理解它们抛出的一大堆数据(令人惊讶的复杂)是很有意义的。 + + + +That being said, cache misses *will* affect the performance of your game. While +you shouldn't spend a ton of time pre-emptively optimizing for cache usage, do +think about how cache-friendly your data structures are throughout the design +process. + +话虽这么说,缓存不命中*仍会*影响你游戏的性能。虽然你不应该花费大量时间有限优化缓存的使用,但是在设计过程中仍要思考你的数据结构是不是对缓存友好。 + +## Keep in Mind + +## 记住。 + +One of the hallmarks of software architecture is *abstraction*. A large chunk of +this book is about patterns to decouple pieces of code from each other so that +they can be changed more easily. In object-oriented languages, this almost +always means interfaces. + +软件体系结构的特点之一是*抽象*。这本书很多的章节都在谈论如何解耦代码块这样它们可以更容易的进行改变。在面向对象的语言中,这几乎总是指代接口。 + +In C++, using interfaces implies accessing objects through pointers or references. But going through a pointer means +hopping across memory, which leads to the cache misses this pattern works to +avoid. + +在C++中,使用接口意味着通过指针或者引用访问对象。但是通过指针就意味在内存中跳跃,这就带来了这章想要避免的缓存不命中。 + + + +In order to please this pattern, you will have to sacrifice some of your +precious abstractions. The more you design your program around data locality, +the more you will have to give up inheritance, interfaces, and the benefits +those tools can provide. There's no silver bullet here, only challenging +trade-offs. That's what makes it fun! + +为了讨好这个模式,你需要牺牲一些你宝贵的抽象。你越围绕数据局部性设计程序,你就越放弃继承接口和这些东西带来的好处。没有银弹,只有挑战的交换。这就是乐趣所在! + +## Sample Code + +## 示例代码 + +If you really go down the rathole of optimizing for data locality, you'll +discover countless ways to slice and dice your data structures into pieces your +CPU can most easily digest. To get you started, I'll show an example for each of +a few of the most common ways to organize your data. We'll cover them in the +context of some specific part of a game engine, but (as with other patterns), +keep in mind that the general technique can be applied anywhere it fits. + +如果你真的要一探数据局部性优化的鼠洞,那么你会发现无数的方法去分割数据结构,将其切为CPU更好处理的小块。为了热热身,我会先从一些最通用的组织数据的方法开始。我们会在游戏引擎的特定部分介绍它们,但是(就像其他章节一样)记住这些通用方法也能在其他部分使用。 + +### Contiguous arrays + +### 连续数组 + +Let's start with a game loop that +processes a bunch of game entities. Those entities are decomposed into different +domains -- AI, physics, and rendering -- using the Component pattern. Here's the `GameEntity` class: + +让我们从处理一系列游戏实体的游戏循环开始。这些实体被分解到不同的领域——AI,物理,渲染——使用了组件模式。这里是`GmaeEntity`类。 + +^code game-entity + +Each component has a relatively small amount of state, maybe little more than a +few vectors or a matrix, and then a method to update +it. The details aren't important here, but imagine something roughly along the +lines of: + +每一个组件都有相对较少的状态,也许只有几个向量或一个矩阵,然后会有一个方法去更新它。这里的细节无关紧要,但是想象一下,事情大概是这样发展的: + + + +^code components + +The game maintains a big array of pointers to all of the entities in the world. +Each spin of the game loop, we need to run the following: + +游戏管理游戏世界中一大堆实体的指针数组。每一个游戏循环,我们都要做如下事情: + + 1. Update the AI components for all of the entities. + + 1. 为每个实体更新他们的AI组件。 + + 2. Update the physics components for them. + + 2. 为每个实体更新他们的物理组件。 + + 3. Render them using their render components. + + 3. 为每个实体更新他们的渲染组件。 + +Lots of game engines implement that like so: + +很多游戏引擎以这种方式实现: + +^code game-loop + +Before you ever heard of a CPU cache, this looked totally innocuous. But by now, +you've got an inkling that something isn't right here. This code isn't just +thrashing the cache, it's taking it around back and beating it to a pulp. Watch +what it's doing: + +在你听说CPU缓存之前,这些看上去完全无害。但是现在,你得看到这里有隐藏着的不对之处。这个带不只是在颠簸缓存,它是四处乱晃然后猛烈的敲击着。看看它做了什么: + + 1. The array of game entities is storing *pointers* to them, so for each element in the + array, we have to traverse that pointer. That's a cache miss. + + 1. 游戏实体的数组存储的是他们的*指针*,所有每一个数组中的游戏实体,我们得使用指针。缓存不命中。 + + 2. Then the game entity has a pointer to the component. Another cache miss. + + 2. 然后游戏实体有组件的指针,又一次缓存不命中。 + + 3. Then we update the component. + + 3. 然后我们更新组件。 + + 4. Now we go back to step one for *every component of every entity in the + game*. + + 4. 再然后我们退回第一布,为*每一个游戏中的实体*做这件事。 + +The scary part is that we have no idea how these objects are laid out in memory. +We're completely at the mercy of the memory manager. As entities get allocated +and freed over time, the heap is likely to become increasingly randomly +organized. + +令人害怕的是,我们组织到这些对象是如何在内存中布局的。我们完全由内存管理器摆布。随着实体的分配和释放,堆会组织更加乱。 + + + +A tangled mess of objects strewn randomly through memory with pointers wiring them all together. + + + +If our goal was to take a whirlwind tour around the game's address space like +some "256MB of RAM in Four Nights!" cheap vacation package, this would be a +fantastic deal. But our goal is to run the game quickly, and traipsing all over main memory is *not* the way to do that. +Remember that `sleepFor500Cycles()` function? Well this code is effectively +calling that *all the time*. + +如果我们的目标是在游戏地址空间中像“四晚尽享256MB”的廉价假期包一样刮起旋风,这也许是一个很好的决定。但是我们的目标是让游戏跑得尽可能快,而在主存各种遍历*不是*一个好办法。记得`sleepFor500Cycles()`函数吗?那个函数*总是*可以认为是更有效率的。 + + + +Let's do something better. Our first observation is that the only reason we +follow a pointer to get to the game entity is so we can immediately follow +*another* pointer to get to a component. `GameEntity` itself has no interesting +state and no useful methods. The *components* are what the game loop cares +about. + +让我们做的更好。我们的第一个发现是我们跟着指针去寻找一个游戏实体的唯一原因是我们可以立刻跟着*另一个*指针去获得一个组件。`GameEntity`本身没有有意义的状态和有用的方法。*组件*才是游戏循环需要的。 + +Instead of a giant constellation of game entities and components scattered +across the inky darkness of address space, we're going to get back down to +Earth. We'll have a big array for each type of component: a flat array of AI +components, another for physics, and another for rendering. + +众多实体和组件不能像星星散落在黑暗天空布置,我们得脚踏实地。我们将每种组件存入一个巨大的数组:一个数组给AI组件,一个给物理,另一个给渲染。 + +Like this: + +就像这样: + + + +^code component-arrays + + + +Let me stress that these are arrays of *components* and not *pointers to +components*. The data is all there, one byte after the other. The game loop can +then walk these directly: + +让我强调一点,这些都是*组件*的数组,而不是*指向组件的指针*数据都在那里,一个接着一个。游戏循环现在可以直接遍历它们了。 + + + +^code game-loop-arrays + + + +We've ditched all of that pointer chasing. Instead of skipping around in memory, +we're doing a straight crawl through three contiguous arrays. + +我们消除了所有的指针跳转。不是在内存中跳来跳去,我们直接在三个数组中做直线的遍历。 + +An array for each of three different kinds of components. Each array neatly packs its components together. + +This pumps a solid stream of bytes right into the hungry maw of the CPU. In my +testing, this change made the update loop *fifty times* faster than the previous +version. + +这将一股字节流直接泵到了CPU饥饿的肚子里。在我的测试中,这个改写后的更新循环是之前性能的*50*倍。 + +Interestingly, we haven't lost much encapsulation here. Sure, the game loop is +updating the components directly instead of going through the game entities, but +it was doing that before to ensure they were processed in the right order. Even +so, each component itself is still nicely encapsulated. It owns its own data and +methods. We simply changed the way it's used. + +有趣的是,我们并没有在这里损失很多封装。是的游戏玄幻直接更新的游戏组件而没有通过游戏实体,但它在此之前确保了它们以正确的方式运行。即使如此,每个组件内部还是很好的封装了。这取决于它们自己的数据和方法。我们只是改变了它使用的方法。 + +This doesn't mean we need to get rid of `GameEntity` either. We can leave it as it +is with pointers to its components. They'll just point into those +arrays. This is still useful for other parts of the game where you want to pass +around a conceptual "game entity" and everything that goes with it. The +important part is that the performance-critical game loop sidesteps that and +goes straight to the data. + +这也不意味着我们摆脱了`GameEntity`。它拥有它组件指针这一状态仍然得以保持。它的组件指针现在只是指到了这个数组之中。对游戏的其他部分,如果你还是想传递一个“游戏实体”,一切照旧。重要的是性能攸关的游戏循环部分回避了直接获取数据这一点。 + +### Packed data + +### 打包数据 + +Say we're doing a particle system. Following the advice of the previous section, +we've got all of our particles in a nice big contiguous array. Let's wrap it in +a little manager class too: + +假设我们在做一个粒子系统。根据上一节的建议,我们将所有的粒子放在一个巨大的连续数组中。让我们用一个管理类包住它。 + + + +^code particle-system + +A rudimentary update method for the system just looks like this: + +系统中的基本更新方法看起来是这样的: + +^code update-particle-system + +But it turns out that we don't actually need to process *all* of the particles +all the time. The particle system has a fixed-size pool of objects, but they +aren't usually all actively twinkling across the screen. The easy answer is +something like this: + +但结果是我们不需要同时更新*所有的*粒子。粒子系统具有固定大小的对象池,但是他们通常不是同时活跃在屏幕上。最简单的解决方案是这样的: + +^code particles-is-active + +We give `Particle` a flag to track whether its in use or not. In the update +loop, we check that for each particle. That loads the +flag into the cache along with all of that particle's other data. If the +particle *isn't* active, then we skip over it to the next one. The rest +of the particle's data that we loaded into the cache is a waste. + +我们给`Particle`一个标志位来追踪其是否在使用状态。在更新循环时,我们检查每个粒子的这一位。这会将粒子其他部分的时间也加载到缓存中。如果粒子*没有*在使用,那么我们跳过它去检查下一个。我们加载到内存中粒子的其他数据都是浪费。 + +The fewer active particles there are, the more we're skipping across memory. The +more we do that, the more cache misses there are between actually doing useful +work updating active particles. If the array is large and has *lots* of inactive +particles in it, we're back to thrashing the cache again. + +活跃的粒子越少,我们要在内存中跳过的部分就越多。我们越这样做,在两次实际有用的更新活跃粒子之间的缓存不命中就越多。如果数组很大又有*很多*不活跃的粒子,我们又在颠簸缓存了。 + +Having objects in a contiguous array doesn't solve much if the objects we're +actually processing aren't contiguous in it. If it's littered with inactive +objects we have to dance around, we're right back to the original problem. + +如果对象不是连续处理的,使用一个连续的数组实际上不能解决太多问题。如果有太多不活跃的对象需要跳过,我们又回到了原来的问题。 + + + +Given the title of this section, you can probably guess the answer. Instead of +*checking* the active flag, we'll *sort* by it. We'll keep all of the active +particles in the front of the list. If we know all of those particles are +active, we don't have to check the flag at all. + +监狱本节的标题,你大概可以猜出答案是什么了。我们不*监测*活跃与否的标签,我们*排序*它。我们将所有活跃的粒子放在列表的前头。如果我们知道了这些粒子都是活跃的,我们就不必再检查这些标识位了。 + +We can also easily keep track of how many active particles there are. With this, +our update loop turns into this thing of beauty: + +我们还可以很容易的追踪有多少活跃的粒子。通过这个,我们的更新循环变成了这种美丽的东西: + +^code update-particles + +Now we aren't skipping over *any* data. Every byte that gets sucked into the +cache is a piece of an active particle that we actually need to process. + +现在我们没有跳过*任何*数据。加载入缓存的每一字节都是我们需要处理的粒子的一部分。 + +Of course, I'm not saying you should quicksort the entire collection of +particles every frame. That would more than eliminate the gains here. What we +want to do is *keep* the array sorted. + +当然,我并没有说你要每帧都要对整个数组做快排。这将抵消这里的收益。我们想要的是*保持*数组排好序。 + +Assuming the array is already sorted -- and it is at first when all particles +are inactive -- the only time it can become *un*sorted is when a particle has +been activated or deactivated. We can handle those two cases pretty easily. When +a particle gets activated, we move it up to the end of the active particles by +swapping it with the first *in*active one: + +假设数组已经排好序了——开始时当所有的粒子都不活跃——它变成未排序的时候即是粒子被激活或者被关闭时。我们可以很轻易的处理着两种情况。当一个粒子激活时,我们将它与第一个*不*活跃粒子交互位置,这样将其移动到激活粒子序列的尾端。 + +^code activate-particle + +To deactivate a particle, we just do the opposite: + +为了关闭粒子,我们只需做相反的事情: + +^code deactivate-particle + +Lots of programmers (myself included) have developed allergies to moving things +around in memory. Schlepping a bunch of bytes around *feels* heavyweight +compared to assigning a pointer. But when you add in the cost of +*traversing* that pointer, it turns out that our intuition is sometimes wrong. +In some cases, it's cheaper to push things around in +memory if it helps you keep the cache full. + +很多程序员(包括我在内)已经对于在内存中移动数据过敏了。将一堆数据移来移去*感觉*比发送一个指针要消耗大得多。但是如果你加上了*解析*指针的代价,这证明有时候我们的估算是错误的。在有些情况下,如果能够保持缓存命中,将一些数据移动消耗更小。 + + + +There's a neat consequence of keeping the particles *sorted* by their active +state -- we don't need to store an active flag in each particle at all. It can be +inferred by its position in the array and the `numActive_` counter. This makes +our particle objects smaller, which means we can pack more in our cache lines, +and that makes them even faster. + +将粒子根据激活状态保持为*排序*——我们就不需要给内阁粒子都添加激活标志位了。这可以有它在数组中的位置和`numActive_`计数器推断而得。这让我们的粒子对象更加小,这就意味着我们在cache lines中能够打包更多的数据,这让他们跑得更快。 + +It's not all rosy, though. As you can see from the API, we've lost a bit of +object orientation here. The `Particle` class no longer controls its own active +state. You can't call some `activate()` method on it since it doesn't know +its index. Instead, any code that wants to activate particles needs access to +the particle *system*. + +但是这并不都是鲜花。你可以从API看出,我们失去了一定的面向对象。`Particle`类不再控制其激活状态了。你不能在他上面调用`activate()`因为他不知道自己的索引。相反,任何想要激活粒子的代码都需要接触到粒子*系统*。 + +In this case, I'm OK with `ParticleSystem` and `Particle` being tightly tied +like this. I think of them as a single *concept* spread across two physical +*classes*. It just means accepting the idea that particles are *only* meaningful +in the context of some particle system. Also, in this case it's likely to be the +particle system that will be spawning and killing particles anyway. + +在这个例子中,我对于`ParticleSystem`和`Particle`像这样牢牢的绑在一起没有问题。这只是我将太慢思维两个跨物理*类*的概念。这只是意味着要接受粒子只在*特定*的粒子系统中有意义。同样,在这种情况下,很可能是粒子系统在复制和销毁粒子。 + +### Hot/cold splitting + +### 热/冷 分割 + +OK, this is the last example of a simple technique for making your cache +happier. Say we've got an AI component for some game entity. It has some state +in it -- the animation it's currently playing, a goal position it's heading +towards, energy level, etc. -- stuff it checks and tweaks every single frame. +Something like: + +这里是最后一种雀跃你缓存的技术例子来。假设我们为某些游戏实体有一个AI控件。他其中包括一些状态——现在正在播放的动画,正在前往的方向,能量等级,等等——这些东西每帧都会发生变化。就像这样: + +^code ai-component + +But it also has some state for rarer eventualities. It stores some data +describing what loot it drops when it has an unfortunate encounter with the +noisy end of a shotgun. That drop data is only used once in the entity's +lifetime, right at its bitter end: + +但它也有一些罕见事件的状态。他存储了一些数据描述它在遭遇到一阵猎枪的嘈杂后会掉落什么战利品。掉落数据在实体的整个生命周期智慧使用一次,就在它结束的前一霎那: + +^code loot-drop + +Assuming we followed the earlier patterns, when we update these AI components, +we walk through a nice packed, contiguous array of data. But that data includes +all of the loot drop information. That makes each component bigger, which +reduces the number of components we can fit in a cache line. We get more cache misses +because the total memory we walk over is larger. The loot data gets pulled into +the cache for every component in every frame, even though we aren't even touching +it. + +假设我们遵循了前面的章节,当我们更新AI组件时,我们穿过了一序列打包好的连续数组。但是那个数据包含 了所有掉落物的信息。这让每个组件都变得更大了,这就减少了我们能够加载到cache line中的组件个数。每一帧每一个组件都会将战利品数据加载到内存中去,即使我们根本不会去使用它。 + +The solution for this is called "hot/cold splitting". The idea is to break our +data structure into two separate pieces. The first holds the "hot" data, the +state we need to touch every frame. The other piece is the "cold" data, +everything else that gets used less frequently. + +这里的解决方案被称为“热/冷分割”。这个电子来源于将我们的数据结构划分为两个分离的部分。第一部分保存“热”数据,那些我们每帧都要调用的数据。剩下的片段被称为“冷”数据,任何在那里的数据都是使用的更少。 + +The hot piece is the *main* AI component. It's the one we need to use the most, +so we don't want to chase a pointer to find it. The cold component can be off to +the side, but we still need to get to it, so we give the hot component a pointer +to it, like so: + +热的部分是AI组件的*主体*。那是我们需要使用最多的部分,所以我们不希望解析一个指针去找到它。冷冷组件可以被归到一边去,但是我们还是需要获得之,因此我们在热组件中包含一个指向它的指针,就像这样: + +^code hot-cold + +Now when we're walking the AI components every frame, the only data that gets +loaded into the cache is stuff we are actually processing (with the exception of that one little pointer to the cold data). + +现在我们每一帧都要遍历AI组件,加载到内存的数据只包含我们需要的数据(以及那个指向冷数据的指针)。 + + + +You can see how this starts to get fuzzy, though. In my example here, it's +pretty obvious which data should be hot and cold, but in a real game it's +rarely so clear-cut. +What if you have fields that are used when an entity is in a certain mode but +not in others? What if entities use a certain chunk of data only when they're in +certain parts of the level? + +你可以看到事情这里是怎么变得模棱两可的。在我的例子中,那些是冷数据,哪些是热数据是很明确的,但是在真实的游戏中一般很少会这么明显的分割出来。如果你有一部分数据,实体在一种状态下会经常使用,另一种状态则不会,那该怎么办?如果实体只在特定等级的时候使用特定的一块数据,又该怎么办? + +Doing this kind of optimization is somewhere between a black art and a rathole. +It's easy to get sucked in and spend endless time pushing data around to see +what speed difference it makes. It will take practice to get a handle on where +to spend your effort. + +做这种优化有时就是在黑色艺术和老鼠洞之间走钢丝。很容易陷入其中然后消耗无尽的时间把数据挪来挪去看看性能如何。需要实践来掌握在哪里付出努力。 + +## Design Decisions + +## 设计决策 + +This pattern is really about a mindset -- it's getting you to think about your +data's arrangement in memory as a key part of your game's performance story. The +actual concrete design space is wide open. You can let data +locality affect your whole architecture, or maybe it's just a localized +pattern you apply to a few core data structures. + +这章真的有关于思维定势——思考你数据的组织模式是你游戏性能的关键部分。实际上具体的设计空间是开放的。你可以让数据局部性影响你的整个架构,或者只在局部几个核心的数据结构上使用这个模式。 + +The biggest questions you'll need to answer are when and where you apply this +pattern, but here are a couple of others that may come up. + +你需要回答的最大问题是何时何地需要你使用这个模式,但是这里还有其他几个问题需要回答。 + + + +### How do you handle polymorphism? + +### 你如何处理多态? + +Up to this point, we've avoided subclassing and virtual methods. We have assumed we +have nice packed arrays of *homogenous* objects. That way, we know they're all +the exact same size. But polymorphism and dynamic dispatch are useful tools +too. How do we reconcile this? + +到了现在,我们回避了子类和虚机制。我们假设我们有打包好的同样的对象。这种情况下,我们知道他们都有同样的大小。但是多态和动态调用也是有用的工具。我们如何调和呢? + + * **Don't:** + + * *别这么干* + + The simplest answer is to avoid subclassing, + or at least avoid it in places where you're optimizing for cache usage. + Software engineer culture is drifting away from heavy use of inheritance + anyway. + + 最简单的解决方案是避免子类,至少在你做内存优化的部分避免使用。无论如何,软件工程师文化已经和大量使用继承渐行渐远了。 + + + + * *It's safe and easy.* You know exactly what class you're dealing with, + and all objects are obviously the same size. + + * *简洁安全。*你知道你在处理什么类,所有的对象都明显是同样的体积。 + + * *It's faster.* Dynamic dispatch means looking up the method in the + vtable and then traversing that pointer to get to the actual code. While + the cost of this varies widely across different hardware, there is *some* cost to dynamic dispatch. + + * *更快*动态调用意味着在跳转表中寻找方法,然后跟着指针寻找特定的代码。这种消耗在不同的硬件区别很大,但是动态调用总会消耗*一些*代价。 + + + + * *It's inflexible.* Of course, the reason we use dynamic dispatch is + because it gives us a powerful way to vary behavior between objects. If + you want different entities in your game to have their own rendering + styles or their own special moves and attacks, virtual methods are a + proven way to model that. Having to instead stuff all of that code into + a single non-virtual method that does something like a big `switch` gets + messy quickly. + + * *不灵活*当然,我们使用动态调用的原因就是它给了我们强大的方式在不同对象间展示不同的行为。如果你的游戏想要不同的实体使用相同的渲染方式或者独特的移动和攻击,虚方法被证明是处理它的好方法。把他换成一个包含巨大的`switch`的非虚方法会超级慢。 + + * **Use separate arrays for each type:** + + * *为每种类型使用分离的数组:* + + We use polymorphism so that we can invoke behavior on an object whose type + we don't know. In other words, we have a mixed bag of stuff, and we want each + object in there to do its own thing when we tell it to go. + + 我们使用多态,这样即使我们不知道对象的类型,我们也能引入行为。换言之,我们有了一堆混合的东西,当我们通知时,我们想要每个对象去做自己的事情。 + + But that raises the question of why mix the bag to begin with? Instead, + why not maintain separate, homogenous collections for each type? + + 但是这提出来为什么开始的时候要把它们混在一起呢?取而代之,为什么不为每种类型保持一个单独的集合呢? + + * *It keeps objects tightly packed.* Since each array only contains + objects of one class, there's no padding or other weirdness. + + * *对象被紧密的排列着。*每一个数组只包含同类的对象,这里没有其他填充或者古怪之处。 + + * *You can statically dispatch.* Once you've got objects partitioned by + type, you don't need polymorphism at all any more. You can use + regular, non-virtual method calls. + + * *静态调度*一旦你获得了对象的类型,你不必在所有时候使用多态。你可以使用通常的,非虚方法调用。 + + * *You have to keep track of a bunch of collections.* If you have a lot of + different object types, the overhead and complexity of maintaining + separate arrays for each can be a chore. + + * *你的追踪每一个集合。*如果你有很多不同的类型,这种过度的,复杂的管理每一种类型分别的类型可是件苦差事。 + + * *You have to be aware of every type*. Since you have to maintain + separate collections for each type, you can't be decoupled from the + *set* of classes. Part of the magic of polymorphism is that it's + *open-ended* -- code that works with an interface can be completely + decoupled from the potentially large set of types that implement that + interface. + + * *你得明了每种类型。*由于你的为每种类型管理分离的集合,你无法解耦*类型集合*。多态的魔力之一在于它是*开放的*——与一个接口交互的代码可以与实现此接口的众多类型解耦。 + + * **Use a collection of pointers:** + + * *使用指针的集合:* + + If you weren't worried about caching, this is the natural solution. Just + have an array of pointers to some base class or interface type. You get all the + polymorphism you could want, and objects can be whatever size they want. + + 如果你不太考虑缓存,这是自然的解法。只要一个指针数组保存基本的类或者接口类型。你获得了所有你想要的多态,以及他们想多大多大的对象。 + + * *It's flexible.* The code that consumes the collection can work with + objects of any type as long as it supports the interface you care about. + It's completely open-ended. + + * *灵活*。这样构建集合的代码可以与任何支持接口的类工作。完全的开放式。 + + * *It's less cache-friendly.* Of course, the whole reason we're discussing + other options here is because this means cache-unfriendly pointer + indirection. But, remember, if this code isn't performance-critical, + that's probably OK. + + * *对缓存不友好*。当然,我们讨论在这里讨论其他选项的原因就是指针的缓存不友好跳转。但是,记住,如果代码不是性能攸关的,这很有可能是行得通的。 + +### How are game entities defined? + +### 游戏实体是如何定义的? + +If you use this pattern in tandem with the Component pattern, you'll have nice contiguous arrays for +all of the components that make up your game entities. The game loop will be +iterating over those directly, so the object for the game entity itself is less +important, but it's still useful in other parts of the codebase where you want +to work with a single conceptual "entity". + +如果与组件模式串联使用这个模式,你会获得组成你游戏实体的组件的多个数组。游戏循环会在那里直接遍历他们,所以有些实体本身就不是那么重要了,但是在其他你想要以一个统一“实体”交互的代码库部分,还是很有用的。 + +The question then is how should it be represented? How does it keep track of its +components? + +这里的问题是它该如何被表示》如何追踪这些组件? + + * **If game entities are classes with pointers to their components:** + + * *如果游戏实体是拥有他们组件指针的类:* + + This is what our first example looked like. It's sort of the vanilla OOP + solution. You've got a class for `GameEntity`, and it has pointers to the + components it owns. Since they're just pointers, it's agnostic about where + and how those components are organized in memory. + + 这是我们第一个例子的样子。这是完全的OOP解决方案。你获得了`GameEntity`类,以及指向它拥有的组件的指针。由于他们只是指针,那就不知道这些组件是如何在内存中组织的了。 + + * *You can store components in contiguous arrays.* Since the game entity + doesn't care where its components are, you can organize them in a nice + packed array to optimize iterating over them. + + * *你可以将实体存储到连续数组中。*既然游戏实体不在乎他的组件在哪里,你可以将它们都组织到一个个好好打包的数组中来管理他们的遍历。 + + * *Given an entity, you can easily get to its components.* They're just a + pointer indirection away. + + * *拿到一个实体,你可以轻易的获得他的组件。*他们就在一次指针跳转后的位置。 + + * *Moving components in memory is hard.* When components get enabled or + disabled, you may want to move them around in the array to keep the + active ones up front and contiguous. If you move a component while the + entity has a raw pointer to it, though, that pointer gets broken if you + aren't careful. You'll have to make sure to update the entity's pointer + at the same time. + + * *在内存中移动组件很难。*当组件启用或者关闭时,你可能想要在数组中移动他们来保证启用的部件在前面连续。如果你在实体有一指针指向其时直接移动它,一不小心指针就会损毁。你得保证同时更新组件的指针。 + + * **If game entities are classes with IDs for their components:** + + * *如果游戏实体拥有他们组件的ID:* + + The challenge with raw pointers to components is that it makes it harder to + move them around in memory. You can address that by using something more + abstract: an ID or index that can be used to *look up* a component. + + 直接使用指针的挑战在于在内存中移动它是困难的。你可以使用更加直接的方案解决之:使用ID或者索引来*查找*组件 + + The actual semantics of the ID and lookup process are up to you. It could be + as simple as storing a unique ID in each component and walking the array, or + more complex like a hash table that maps IDs to their current index in the + component array. + + ID的实际查找过程是由你决定的,他可能很简单,只需要为每一个实体保存一个独特的ID然后遍历数组,或者更加复杂地使用哈希表就爱那个ID映射到到他们组件的现有位置。 + + * *It's more complex.* Your ID system doesn't have to be rocket science, + but it's still more work than a basic pointer. You'll have to implement + and debug it, and there will be memory overhead for bookkeeping. + + * *更复杂*你的ID系统不必是高科技,但是他韩式需要比指针多做一些事情。你得实现它然后排出漏洞,然后这里消耗内存开销。 + + * *It's slower*. It's hard to beat traversing a raw pointer. There may be + some searching or hashing involved to get from an entity to one + of its components. + + * *更慢*。很难比直接使用指针更快。这里也许有一些搜索或者哈希来帮助一个实体找到它的组件。 + + * *You'll need access to the component "manager".* The basic idea is that + you have some abstract ID that identifies a component. You can use it to + get a reference to the actual component object. But to do that, you need + to hand that ID to something that can actually find the component. That + will be the class that wraps your raw contiguous array of component + objects. + + * *你需要接触组件“管理器”*基本的主意是你拥有一个抽象的ID标识一个组件。你可以使用它来获得对应组件对象的引用。但是为了做到这一点,你需要处理ID找到组件的部分。这也许是一个包裹着你整个连续组件数组的对象。 + + With raw pointers, if you have a game entity, you can find its + components. With this, you need the game + entity *and the component registry too*. + + 通过裸指针,如果你有一个游戏实体,你可以直接找到组件,而通过这种方式你需要接触游戏实体和*组件注册器*。 + + + + * **If the game entity is *itself* just an ID:** + + * *如果游戏实体*本身*就是一个ID:* + + This is a newer style that some game engines use. Once you've moved all of + your entity's behavior and state out of the main class and into components, + what's left? It turns out, not much. The only thing an entity does is bind a + set of components together. It exists just to say *this* AI component and + *this* physics component and *this* render component define one living + entity in the world. + + 这是一种某些游戏引擎使用的新方式。一旦你将实体的行为和状态移出主类放入组件,还剩什么呢?事实上,没什么了。实体干的唯一事情就是将组件链接在一起。他的存在只是为了说明*这个*AI组件和*这个*物理组件还有*这个*渲染组件定义了一个存在于游戏世界的实体。 + + That's important because components interact. The render component needs to + know where the entity is, which may be a property of the physics component. + The AI component wants to move the entity, so it needs to apply a force to + the physics component. Each component needs a way to get the other sibling + components of the entity it's a part of. + + 这一点很重要因为组件要相互交互。渲染组件需要知道实体在哪里,这也许是物理组件的属性。AI组件想要移动实体,因此他需要对物理组件施加力。每一个组件都需要一种方式获得同在一个实体中的其他组件。 + + Some smart people realized all you need for that is an ID. Instead of the + entity knowing its components, the components know their entity. Each + component knows the ID of the entity that owns it. When the AI component + needs the physics component for its entity, it simply asks for the physics + component with the same entity ID that it holds. + + 有些聪明人意识到你需要的唯一东西就是ID。不是实体知道组件,而是组件知道实体。每一个组件知道拥有它的实体的ID。当AI组件需要它所属实体的物理组件时,它只需要找到那个拥有同样ID的物理组件。 + + Your entity *classes* disappear entirely, replaced by a glorified wrapper + around a number. + + 你的实体*类*整个消失,取而代之的是一个围绕数字的华而不实的包装。 + + * *Entities are tiny.* When you want to pass around a reference to a game + entity, it's just a single value. + + * *实体很小。*当你想要传递游戏实体的引用是,只需一个简单的值。 + + * *Entities are empty.* Of course, the downside of moving everything out + of entities is that you *have* to move everything out of entities. You + no longer have a place to put non-component-specific state or behavior. + This style doubles down on the Component pattern. + + * *实体是空的。*担任,将所有东西移出实体的代价是你*必须*将所有东西移出来。你不能再拥有组件独有的状态和行为,这种行为双管齐下的解决了组件模式。 + + * *You don't have to manage their lifetime.* Since entities are just dumb + value types, they don't need to be explicitly allocated and freed. An + entity implicitly "dies" when all of its components are destroyed. + + * *你不必在管理他们的生命周期。*由于实体只是内置值的类型,他们不需要被明确的分配和释放。当它所有的组件都被释放时,一个对象就含蓄的“死”了。 + + * *Looking up a component for an entity may be slow.* This is the same + problem as the previous answer, but in the opposite direction. To find a + component for some entity, you have to map an ID to an object. That + process may be costly. + + * *查找实体的某一组件也许会很慢。*这和前一方案有相同的问题,但是是在相反的方向。为了给某一个实体找组件,你需要给对象分一个ID。这一过程也许消耗很大。 + + This time, though, it *is* performance-critical. Components often + interact with their siblings during update, so you will need to find + components frequently. One solution is to make the "ID" of an entity + the index of the component in its array. + + 但是,这一次,这*是*性能攸关的。在更新时,组件经常与他的兄弟组件交互,因此你需要经常的查找组件。我们的解法是让实体的“ID”作为他组件在数组中的索引。 + + If every entity has the same set of components, then your component + arrays are completely parallel. The component in slot three of the AI + component array will be for the same entity that the physics component + in slot three of *its* array is associated with. + + 如果每一个实体都拥有相同集合的组件,那么你的组件数组就是完全同步的。组件数组三号位的AI组件与在*物理组件*数组三号位的组件相关联。 + + Keep in mind, though, that this *forces* you to keep those arrays in + parallel. That's hard if you want to start sorting or packing them by + different criteria. You may have some entities with disabled physics and + others that are invisible. There's no way to sort the physics and render + component arrays optimally for both cases if they have to stay in sync + with each other. + + 但是,记住,这*强迫*你保持这些数组同步。如果你想要用不同的方式排序或者打包他们就会变得很难。你也许需要一些没有物理组件或者隐形的实体。他们得保证与其他同步,这样没有办法独自排序物理和渲染组件数组。 + +## See Also + +## 参见 + + * Much of this chapter revolves around the Component pattern, and that pattern is definitely one of the + most common data structures that gets optimized for cache usage. In fact, + using the Component pattern makes this optimization easier. Since entities + are updated one "domain" (AI, physics, etc.) at a time, splitting them out + into components lets you slice a bunch of entities into the right + pieces to be cache-friendly. + + 这一章大部分围绕着组件模式。,这种模式绝对是一个最常见的数据结构被为缓存优化的例子。事实上,使用组件模式让这种优化变容易了。由于实体是一个一个“域”(AI,物理,等等)更新的,将它们划出去变成组件让你更容易将它们保存为缓存友好的合适大小。 + + But that doesn't mean you can *only* use this pattern with components! Any + time you have performance-critical code that touches a lot of data, it's + important to think about locality. + + + 但是这不意味着你*只能*为组件使用这种模式!任何时候你有需要接触很多数据的性能攸关的代码,考虑局部性就是很重要的。 + + * Tony Albrecht's "Pitfalls of Object-Oriented Programming" is probably the + most widely-read introduction to designing your game's data structures for + cache-friendliness. It made a lot more people (including me!) aware of how + big of a deal this is for performance. + + * Tony Albrecht的《Pitfalls of Object-Oriented Programming》也许是最被广泛阅读的指南来为内存友好设计你游戏的指南录。它让很多人(包括我!)明白了这点对于性能是多么重要。 + + * Around the same time, Noel Llopis wrote a [very influential blog + post](http://gamesfromwithin.com/data-oriented-design) on the same topic. + + * 几乎同时,Noel Llopis写了关于同一话题写了一篇非常有影响力的博客。 + + * This pattern almost invariably takes advantage of a contiguous array of + homogenous objects. Over time, you'll very likely be adding and removing + objects from that array. The Object Pool pattern is about exactly that. + + * 这一章几乎完全得益于同类对象的连续存储数组。随着时间的推移,你也许需要向那个数组增加或删除对象。对象池模式正是关于这一点。 + + * The [Artemis](http://gamadu.com/artemis/) game engine is one of the first + and better-known frameworks that uses simple IDs for game entities. + + * 游戏引擎Artemis是第一个也是最著名的为游戏实体使用简单ID的框架。 diff --git a/book/decoupling-patterns.markdown b/book/decoupling-patterns.markdown new file mode 100644 index 0000000..3137980 --- /dev/null +++ b/book/decoupling-patterns.markdown @@ -0,0 +1,31 @@ +^title Decoupling Patterns + +解耦模式 + +Once you get the hang of a programming language, writing code to do what you +want is actually pretty easy. What's hard is writing code that's easy to adapt +when your requirements *change*. Rarely do we have the luxury of a perfect +feature set before we've fired up our editor. + +一旦你获得了编程语言的巧妙,编写你想要写的东西就会变得相当容易。困难的是编写适应需求*变化*的代码,在我们使用文本编辑器开火之前,通常没有完美的特性表使用。 + +A powerful tool we have for making change easier is *decoupling*. When we say +two pieces of code are "decoupled", we mean a change in one usually doesn't +require a change in the other. When you change some feature in your game, the +fewer places in code you have to touch, the easier it is. + +能让我们更好的适应变化的工具是*解耦*。当我们说两块代码“解耦”时,是指修改一块代码一般不会需要修改另一块代码。带我们修改游戏中的一些特性时,我们需要修改的代码越少,就越容易。 + +[Components](component.html) decouple different domains in your game from each +other within a single entity that has aspects of all of them. [Event +Queues](event-queue.html) decouple two objects communicating with each other, +both statically and *in time*. [Service Locators](service-locator.html) let +code access a facility without being bound to the code that provides it. + +[组件模式](component.html)将一个实体拆成多个领域来解耦他们。[事件序列](event-queue.html)解耦了两个互相通信的事物,稳定而且*及时*。[服务定位](service-locator.html)让代码使用服务而无需绑定到提供服务的代码。 + +## The Patterns + +* [Component](component.html) +* [Event Queue](event-queue.html) +* [Service Locator](service-locator.html) diff --git a/book/design-patterns-revisited.markdown b/book/design-patterns-revisited.markdown new file mode 100644 index 0000000..319d193 --- /dev/null +++ b/book/design-patterns-revisited.markdown @@ -0,0 +1,39 @@ +^title Design Patterns Revisited + +# 重访《设计模式》 + +*Design Patterns: Elements of Reusable Object-Oriented Software* is nearly +twenty years old by my watch. Unless you're looking over my shoulder, there's a +good chance *Design Patterns* will be old enough to drink by the time you read +this. For an industry as quickly moving as software, that's practically ancient. +The enduring popularity of the book says something about how timeless design +is compared to many frameworks and methodologies. + +我看《设计模式:可复用面向对象软件的基础》已经二十岁了。除非你踩在我的肩膀上,否则《设计模式》已经酝酿成一坛足以饮用的老酒了。对于像软件行业这样快速发展的行业,这已经是老古董了。这本书讲了很多关于框架和方法的经久不衰的设计方法。 + +While I think *Design Patterns* is still relevant, we've learned a lot in the +past couple of decades. In this section, we'll walk through a handful of the +original patterns the Gang of Four documented. For each pattern, I hope to have +something useful or interesting to say. + +虽然我们在过去几十年一再学习,×设计模式×仍然与本书相关。在这一部分,我们会遇到GoF记载的一些模式。对于每一个模式,我希望能讲出一些有用或者有趣的东西。 + +I think some patterns are overused (Singleton), +while others are underappreciated (Command). A couple +are in here because I want to explore their relevance specifically to games (Flyweight and Observer). +Finally, sometimes I just think it's fun to see how patterns are enmeshed in +the larger field of programming (Prototype and State). + +我认为有些模式被过度使用了(Singleton单例模式),而另一些没有得到应有的应用(Command命令模式)。有些模式在这里是因为我想探索在游戏上的它们特殊应用(Flyweight享元模式Observer观察者模式)。最后,看看有些模式在更大的编程领域是如何运用的有时是很有趣的(Prototype原型模式State状态模式)。 + +## The Patterns + + * [Command](command.html) + * [Flyweight](flyweight.html) + * [Observer](observer.html) + * [Prototype](prototype.html) + * [Singleton](singleton.html) + * [State](state.html) diff --git a/book/dirty-flag.markdown b/book/dirty-flag.markdown new file mode 100644 index 0000000..abe5a91 --- /dev/null +++ b/book/dirty-flag.markdown @@ -0,0 +1,847 @@ +^title Dirty Flag +^section Optimization Patterns + +## Intent + +## 意图 + +*Avoid unnecessary work by deferring it until the result is needed.* + +*将工作延期至其结果需要时才去执行,避免不必要的工作。* + +## Motivation + +## 动机 + +Many games have something called a *scene graph*. This is a big data structure +that contains all of the objects in the world. The rendering engine uses this to +determine where to draw stuff on the screen. + +很多游戏有*场景图*。那是一个巨大的数据结构包含了游戏世界中所有的对象。渲染引擎使用之来决定在屏幕哪里画事物。 + +At its simplest, a scene graph is just a flat list of objects. Each object has a +model, or some other graphic primitive, and a *transform*. The transform describes the object's +position, rotation, and scale in the world. To move or turn an object, we simply +change its transform. + +最简单的实现中场景图只是一个对象列表。每一额无比都有一个模型,或者一些其他的原始图形,以及一个*转换*。一个转换描述了对象的字啊世界中的位置,方向,拉伸。为了移动或者移动一个对象,我们只需简单的改变它的转换。 + + + +When the renderer draws an object, it takes the object's model, applies the +transform to it, and then renders it there in the world. If we had a scene +*bag* and not a scene *graph*, that would be it, and life would be simple. + +当渲染系统描绘一个对象,他取出对下岗的模型,对其使用转换,然后将其渲染到游戏世界中。如果我们有一个场景*包*而不是场景*图*,那就是这样了,生活就很简单。 + +However, most scene graphs are *hierarchical*. +An object in the graph may have a parent object that it is anchored to. In that +case, its transform is relative to the *parent's* position and isn't an +absolute position in the world. + +但是,大多数场景图都是*按等级划分的*。一个图像中的对象也许拥有它锚定的父对象。在这种情况下,他的转换与其*父对象*的位置相关,不再是游戏世界上的绝对位置。 + +For example, imagine our game world has a pirate ship at sea. Atop the ship's +mast is a crow's nest. Hunched in that crow's nest is a pirate. Clutching the +pirate's shoulder is a parrot. The ship's local transform positions the ship in +the sea. The crow's nest's transform positions the nest on the ship, and so on. + +举个例子,将游戏世界想象为海上的海盗船。桅杆的顶端是一个乌鸦巢。在乌鸦巢中的是一个海盗,海盗肩上是一只鹦鹉。船本身的转换定位穿在海上的位置。乌鸦巢的转换定位器在船上的位置,诸如此类。 + + +A pirate ship containing a crow's nest with a pirate in it with a parrot on his shoulder. + + + +This way, when a parent object moves, its children move with it automatically. +If we change the local transform of the ship, the crow's nest, pirate, and +parrot go along for the ride. It would be a total headache if, when the ship moved, we had to manually adjust +the transforms of all the objects on it to keep them from sliding off. + +这样的话,当一个人物对象移动时,他的孩子节点也自动的跟着他移动。吴国我们改变了船本身的转换,乌鸦巢,海盗和鹦鹉都会随之移动。如果当船移动时,我们就得手动调整每一个对象来保持他们不滑出去,那可相当令人头疼。 + + + +But to actually draw the parrot on screen, we need to know its absolute position +in the world. I'll call the parent-relative transform the object's *local +transform*. To render an object, we need to know its *world transform*. + +当时为了在屏幕上真正的猫会鹦鹉,我需要知道它咋世界上的绝对位置。我会我会调用父节点相关的转换对象的*自身转换*。为了渲染一个对象,我需要知道他的*世界转换*。 + +### Local and world transforms + +### 自身转换和世界转换 + +Calculating an object's world transform is pretty straightforward -- you just walk +its parent chain starting at the root all the way down to the object, combining +transforms as you go. In other words, the parrot's world transform is: + +计算对象的世界转换很直接——你只需要从他的父节点一直追踪到对象,将你经过的所有转换绑在一起。换言之,鹦鹉的世界转换如下: + + +The parrot's world position comes from multiplying the local positions for the ship, nest, pirate, and parrot. + + + +We need the world transform for every object in the world every frame, so even +though there are only a handful of matrix multiplications per model, it's on the hot +code path where performance is critical. Keeping them up to date is tricky +because when a parent object moves, that affects the world transform of itself +and all of its children, recursively. + +我们需要为游戏世界的每个对象每一帧做世界转换,因此哪怕每个模型只有一大堆矩阵乘法,当性能攸关时它也在热点代码路径上。保持他们更新是有技巧的,因为当父对象移动时,它影响他自己的世界转换,并递归影响他的所有孩子节点。 + +The simplest approach is to calculate transforms on the fly while +rendering. Each frame, we recursively traverse the scene graph starting at the +top of the hierarchy. For each object, we calculate its world transform right +then and draw it. + +最简单的方法是在渲染时计算转换。每一帧,我们从最高层递归遍历整个场景图。对每一个对象我们计算他的世界转换然后绘制它。 + +But this is terribly wasteful of our precious CPU juice! Many objects in the +world are *not* moving every frame. Think of all of the static geometry that +makes up the level. Recalculating their world transforms each frame even though +they haven't changed is a waste. + +但这完全是在浪费CPU!很多游戏世界的对象*不*在每帧都移动。想想那些构成关卡的静态几何图形。在没有改变的情况下每帧计算他们的世界转换是一种浪费。 + +### Cached world transforms + +### 缓存世界转换 + +The obvious answer is to *cache* it. In each object, we store its local +transform and its derived world transform. When we render, we only use the +precalculated world transform. If the object never moves, the cached transform +is always up to date and everything's happy. + +明显的回答是*缓存*它。在每个对象中,我们存储他的本身转换和他的世界转换。当我们渲染时我们使用预计算的世界转换。如果对象从未移动,缓存的转换永远跟得上变化然后每个人都很开心。 + +When an object *does* move, the simple approach is to refresh its world +transform right then. But don't forget the hierarchy! When a parent moves, we +have to recalculate its world transform *and all of its children's, +recursively*. + +当一个对象*确实*移动了,简单的解决方式是之后就更新他的世界转换。但是不要忘记层次性!当父节点移动时,我们得计算它的世界转换以及*递归计算它所有的子对象*。 + +Imagine some busy gameplay. In a single frame, the ship gets tossed on the +ocean, the crow's nest rocks in the wind, the pirate leans to the edge, and the +parrot hops onto his head. We changed four local transforms. If we recalculate +world transforms eagerly whenever a local transform changes, what ends up +happening? + +想象一些忙碌的游戏。在一帧中,船在海上颠簸,乌鸦巢在风中摇晃,海盗甩到了边缘,而鹦鹉撞上了他的脑袋。我们改变了四个本身转换。如果每一次自身转换都立即更新世界转换,会发生什么? + + +Any time an object moves, the world coordinates are recalculated eagerly and redundantly. + + + +We only moved four objects, but we did *ten* world transform calculations. +That's six pointless calculations that get thrown out before they are ever used +by the renderer. We calculated the parrot's world transform *four* times, but it +is only rendered once. + +我只移动四个对象,但我们做了*十个*世界转换计算。那就有六个在被渲染器使用前浪费了。我们计算了鹦鹉的世界转换*四*次,但它只需渲染一次。 + +The problem is that a world transform may depend on several local transforms. +Since we recalculate immediately each time *one* of the transforms changes, we end up +recalculating the same transform multiple times when more than one of the local +transforms it depends on changes in the same frame. + +问题是一个世界转换也许会依赖于多个自身转换。由于我们*每一次*转换变化就立即重新计算,当自身转换依赖的多个世界转换同时发生变化的时候,我们就对同一转换做了多次重新计算。 + +### Deferred recalculation + +### 延期重计算 + +We'll solve this by decoupling changing local +transforms from updating the world transforms. This lets us change a bunch of +local transforms in a single batch and *then* recalculate the affected world +transform just once after all of those modifications are done, right before we +need it to render. + +我们会通过解耦自身转换和世界转换的更新来解决这个问题。这让我们先在一批中改变自身转换,在这些改变完成*之后*重新计算他们的世界转换,就在我们需要渲染它之前 + + + +To do this, we add a *flag* to each object in the graph. "Flag" and "bit" are +synonymous in programming -- they both mean a single micron of data that can be +in one of two states. We call those "true" and "false", or sometimes "set" and +"cleared". I'll use all of these interchangeably. + +为了做到这一点,我们为图中的每一个对象添加一个*标识位*。“标识”和“位”在编程中密切相关——他们都代表一小块处在两种状态之一的数据。我们称之为“真”和“假”,或者有时“设置”和“清除”。我之后会交替使用它们。 + +When the local transform +changes, we set it. When we need the object's world transform, we check the +flag. If it's set, we calculate the world transform and then clear the flag. The +flag represents, "Is the world transform out of date?" For reasons that aren't +entirely clear, the traditional name for this "out-of-date-ness" is "dirty". +Hence: *a dirty flag*. "Dirty bit" is an equally +common name for this pattern, but I figured I'd +stick with the name that didn't seem as prurient. + +当自身转换改变了,我们设置它。当我们需要对象的世界转换时,我们坚持这一位。如果它被设置了,我们计算世界转换然后清除标识。那个标识代表着,“世界转换过时了吗?”由于他们没有清除,这种“过时的杂乱”被称为“脏”。也就是*脏标识*。“脏位”也是这章通常使用的名字,但是我决定使用不那么下流的称呼。 + + + +If we apply this pattern and then move all of the objects in our previous +example, the game ends up doing: + +如果我们应用这一章然后移动我们之前例子的所有对象,那么游戏最终是这样的: + +By deferring until all moves are done, we only recalculate once. + +That's the best you could hope to do -- the world transform for each affected +object is calculated exactly once. With only a single bit of data, this pattern +does a few things for us: + +这就是您能希望得到的最好结果了——每一个受到影响的对象的世界转换只被计算一次。使用仅仅一位数据,这个模式为我们做了以下事情: + + * It collapses modifications to multiple local transforms along an object's + parent chain into a single recalculation on the object. + + * 他将对象的父节点链上的众多自身转换变化归并成对象上的一次计算。 + + * It avoids recalculation on objects that didn't move. + + * 它避免了在没有移动的对象上重新计算。 + + * And a minor bonus: if an object gets removed before it's rendered, it + doesn't calculate its world transform at all. + + * 还有一个小小的意外收获:如果对象在渲染前被删除了,不必再计算它的世界转换。 + +## The Pattern + +## 模式 + +A set of **primary data** changes over time. A set of **derived data** is +determined from this using some **expensive process**. A **"dirty" flag** tracks +when the derived data is out of sync with the primary data. It is **set when the +primary data changes**. If the flag is set when the derived data is needed, then +**it is reprocessed and the flag is cleared.** Otherwise, the previous **cached +derived data** is used. + +一组*根本数据*随着时间变化而改变。一组*导出数据*使用一些*昂贵的过程*由他们推定。一个*“脏”标识*追踪导出数据是否与根本数据保持一致。它在*基本数据改变时被设置。*否则的话,使用之前的*缓存的导出数据*。 + +## When to Use It + +## 何时使用 + +Compared to some other patterns in this book, this one solves a pretty specific +problem. Also, like most optimizations, you should only reach for it when you +have a performance problem big enough to justify the added code complexity. + +与这本书中的其他模式相比,这一个解决了一个非常特殊的问题。同时,就像其他优化一样,只在有足够大的性能问题时再使用这一章增加代码 复杂度。 + +Dirty flags are applied to two kinds of work: *calculation* and +*synchronization*. In both cases, the process of going from the primary data to +the derived data is time-consuming or otherwise costly. + +脏标识在两种工作上应用:“计算”和“同步”。在两种情况下,从基本数据转换到导出数据消耗很多时间或者有其他方面的消耗。 + +In our scene graph example, the process is slow because of the amount of math to +perform. When using this pattern for synchronization, on the other hand, it's +more often that the derived data is *somewhere else* -- either on disk or over +the network on another machine -- and simply getting it from point A to point B +is what's expensive. + +在我们的场景图例子中,这个过程非常缓慢是因为需要执行很多数学运算。当你使用这个章节为了同步时,另一个应用场景,更通常的是导出数据在*别的地方*——要么在磁盘上或者在网格络另一头的终端机上——从点A到点B消耗很大。 + +There are a couple of other requirements too: + +这里是一些其他应用场景: + + * **The primary data has to change more often than the derived data is used.** + This pattern works by avoiding processing derived data when a subsequent + primary data change would invalidate it before it gets used. If you find + yourself always needing that derived data after every single modification + to the primary data, this pattern can't help. + + * *基本数据的变化速度远高于导出数据的使用速度。*避免在导出数据使用前基本数据再一次变化而需要做的不必要的计算。如果你发现你总是在基本数据变化后使用导出数据,这个模式额密友帮助。 + + * **It should be hard to update incrementally.** Let's say the + pirate ship in our game can only carry so much booty. We need to + know the total weight of everything in the hold. We + *could* use this pattern and have a dirty flag for the total weight. Every + time we add or remove some loot, we set the flag. When we need the + total, we add up all of the booty and clear the flag. + + * *增量更新应该是困难的。*架设我们海盗船只能携带特定数量的战利品。我们需要获取带着的每件事物的总重量。我们*可以*使用这个模式让后为总重量设一个脏标识。每一次我们添加或者移除一些战利品,我们设置这个标识。当我们需要总量,我们将所有战利品重量加起来然后清除标识。 + + But a simpler solution is to *keep a running total*. When we add or + remove an item, just add or remove its weight from the current total. If + we can "pay as we go" like this and keep the derived data updated, then + that's often a better choice than using this pattern and calculating the + derived data from scratch when needed. + + 但是一个更简单的解决方法是*保持计算总量*。当我们添加或删除一个事物,直接从现在的总重量添加或者删除它的重量。如果我们可以*承担得起消耗*保持导出数据更新,那么更好的选择是不用这个模式,每次需要时重新计算导出数据。 + +This makes it sound like dirty flags are rarely appropriate, but you'll +find a place here or there where they help. Searching +your average game codebase for the word "dirty" will often turn up uses of this +pattern. + +这听起来脏标识很少使用,但你会找到一两个部分它会帮得上忙。直接字啊你的邮箱代码库中搜索“dirty”通常会发现使用这个模式。 + + + +## Keep in Mind + +## 记住 + +Even after you've convinced yourself this pattern is a good fit, there are a few +wrinkles that can cause you some discomfort. + +哪怕是在我们说服自己这个模式在这里很恰当,这里还有一些东西可能会让你不舒服。 + +### There is a cost to deferring for too long + +### 延期太久是有代价的 + +This pattern defers some slow work until the result is actually needed, but when +it is, it's often needed *right now*. But the reason we're using this pattern to +begin with is because calculating that result is slow! + +这个模式将一些缓慢的工作延期到结果真正需要的时候,但是当它要的时候,通常是*现在就要*。但是我们之所有使用这个模式的原因是计算结果很慢! + +This isn't a problem in our example because we can still calculate world +coordinates fast enough to fit within a frame, but you can imagine other cases +where the work you're doing is a big chunk that takes noticeable time to chew +through. If the game doesn't *start* chewing until right when the player expects +to see the result, that can cause an unpleasant visible pause. + +在我们的例子中这不是问题,因为我们还是可以在一帧之内计算世界的位置,但是可以想象其他情况下,工作是一大块需要可观时间来进行。如果在玩家想要结构的时候才*开始*计算,这会引起不愉快的可见暂停。 + +Another problem with deferring is that if something goes wrong, you may fail to +do the work at all. This can be particularly problematic when you're using this +pattern to save some state to a more persistent form. + +另一个延期的问题是,如果有东西出错率,你也许根本做不了事情。想要将模式存储为固定模式也许会造成问题。 + +For example, text editors know if your document has "unsaved changes". That +little bullet or star in your file's title bar is literally the dirty flag +visualized. The primary data is the open document in memory, and the derived +data is the file on disk. + +举个例子,文本编辑器知道你的文档有“没保存的改变”。在你文件的标题栏的小点或者星号就是一个可见的脏标识。原始数据是在内存中打开的文档,推导数据是在磁盘上的文件。 + +A window titlebar showing the little icon representing unsaved changes. + +Many programs don't save to disk until either the document is closed or the +application is exited. That's fine most of the time, but if you accidentally +kick the power cable out, there goes your masterpiece. + +很多程序直到文档关闭或者应用退出才保存到磁盘上。在大多数情况下这都很好,但是如果你一不小心踢到了插线板,你的主要工作也就随风而逝了。 + +Editors that auto-save a backup in the background are compensating specifically +for this shortcoming. The auto-save frequency is a point on the continuum +between not losing too much work when a crash occurs and not thrashing the file +system too much by saving all the time. + +在后台自动保存备份的编辑器是这一失误的补偿。这种自动保存的频率保持在在崩溃时不要丢失太多数据和不要总是打扰文件系统保存文件之间。 + + + +### You have to make sure to set the flag *every* time the state changes + +### *每一次*状态改变你都得保证设置标识。 + +Since the derived data is calculated from the primary data, it's essentially a +cache. Whenever you have cached data, the trickiest aspect of it is *cache invalidation* -- correctly noting when the cache is +out of sync with its source data. In this pattern, that means setting the dirty +flag when *any* primary data changes. + +由于推导数据是从基本数据推导而来的,它实际上是缓存。无论何时你缓存了数据,有技巧的部分都是保证*缓存一致性*——在缓存与它原来数据不同步的时候通知之。字啊这种模式上,这意味着*任何*基本数据变化时设置脏标识。 + + + +Miss it in one place, and your program will incorrectly use stale derived data. +This leads to confused players and bugs that are very hard to track down. When you use +this pattern, you'll have to take care that any code that modifies the primary +state also sets the dirty flag. + +一处遗漏,你的程序就使用了不正确的推导数据。这引起了了玩家的困惑和非常难以追踪的漏洞。当你使用合格模式id时候,你也得注意任何修改了原始数据的代码设置了脏标识。 + +One way to mitigate this is by encapsulating modifications to the primary data +behind some interface. If anything that can change the state goes through a +single narrow API, you can set the dirty flag there and rest assured that it +won't be missed. + +一种解决它的方法是将原始数据的修改隐藏在接口之后。任何想要改变状态的代码都要通过一个狭小的API,你可以在哪里设置脏标识来保证不会遗漏。 + +### You have to keep the previous derived data in memory + +### 你得之前的推导数据保存在内存中。 + + + +When the derived data is needed and the dirty flag *isn't* set, it uses the +previously calculated data. This is obvious, but that does imply that you have +to keep that derived data around in memory in case you end up needing it later. + +当推导数据被请求而脏标识*没有*设置,他使用之间计算出的数据。这很明显,但这需要你在内存中保存推导数据在内存中,以防之后你需要再次使用。 + + + +If you weren't using this pattern, you could calculate the derived data on the +fly whenever you needed it, then discard it when you were done. That avoids the +expense of keeping it cached in memory at the cost of having to do that +calculation every time you need the result. + +如果你没有使用这个模式,你可以在你需要的时候计算推导数据,然后在你完成之后释放。这避免了每一次计算后都将其存储回内存的代价。 + +Like many optimizations, then, this pattern trades +memory for speed. In return for keeping the previously calculated data in +memory, you avoid having to recalculate it when it hasn't changed. This +trade-off makes sense when the calculation is slow and memory is cheap. When +you've got more time than memory on your hands, it's better to calculate it +as needed. + +就像很多优化一样,这种模式以内存换速度。通过在内存中保存之前计算的结果值,你避免了在它没有改变的情况下重新计算。这种就爱哦一在内存拍内衣而计算昂贵的时候是划算的。当你手头有更多空闲的时间而不是内存的时候,最好按需求重新计算之。 + + + +## Sample Code + +## 示例代码 + +Let's assume we've met the surprisingly long list of requirements and see how +the pattern looks in code. As I mentioned before, the actual math behind +transform matrices is beyond the humble aims of this book, so I'll just +encapsulate that in a class whose implementation you can presume exists +somewhere out in the æther: + +假设我们满足了出奇长的需求列表,看看模式字啊代码中是如何应用的。就像我在之前提到的那样,矩阵变换后面的数学超出了本书的范围,因此我将其封装在一个类中,假设它们在其他某处实现了: + +^code transform + +The only operation we need here is `combine()` so that we can get an object's +world transform by combining all of the local transforms along its parent chain. +It also has a method to get an "origin" transform -- basically an identity +matrix that means no translation, rotation, or scaling at all. + +这里我们唯一需要的操作就是`combine()`这样我们可以通过将所有父节点链上的自身转换组合起来获得一个对象的世界转换。同样有一个方法来获得“原先”的转换——通常是一个身份矩阵,没有平移,旋转,或者拉伸。 + +Next, we'll sketch out the class for an object in the scene graph. This is the +bare minimum we need *before* applying this pattern: + +下面我们勾勒出场景图中的对象类。这是在我们应用模式*之前*,我们需要的最低限度的东西。 + +^code graph-node + +Each node has a local transform which describes where it is relative to its +parent. It has a mesh which is the actual graphic for the object. (We'll allow +`mesh_` to be `NULL` too to handle non-visual nodes that are used just to group +their children.) Finally, each node has a possibly empty collection of child +nodes. + +每一个节点都有一个原始转换描述了他和它的父节点之间的关系。他有对象的真实网格状结构。(我们将`mesh_`置为`NULL`来处理不可见的用来组织子节点的结点。)最终每一个节点都有一个可能为空的子节点集合。 + +With this, a "scene graph" is really only a single root `GraphNode` whose +children (and grandchildren, etc.) are all of the objects in the world: + +通过这样,一个“场景图”只有一个简单的`GraphNode`,它是世界里所有的子节点(以及孙子节点)的根。 + +^code scene-graph + +In order to render a scene graph, all we need to do is traverse that tree of +nodes, starting at the root, and call the following function for each node's mesh +with the right world transform: + +为了渲染场景图,我们需要的就是遍历节点树,从根开始,然后使用正确的世界变换为每个节点的王调用函数: + +^code render + +We won't implement this here, but if we did, it would do whatever magic the +renderer needs to draw that mesh at the given location in the world. If we can +call that correctly and efficiently on every node in the scene graph, we're +happy. + +我们还没有直接在这里实现,但是如果我们这么做了,那么它就会做渲染需要的事情将那张网格绘制在世界上给定的位置。如果我们对场景图中的每一个节点都正确有效的调用,我们就很高兴了。 + +### An unoptimized traversal + +### 一个没有优化的遍历 + +To get our hands dirty, let's throw together a basic traversal for rendering the +scene graph that calculates the world positions on the fly. It won't be optimal, +but it will be simple. We'll add a new method to `GraphNode`: + +为了把我们的手弄脏,我们做一个简单的遍历,在渲染需要的时候去计算所有的位置。这没有优化,但它很简单。我们添加一个新方法给`GraphNode`: + +^code render-on-fly + +We pass the world transform of the node's parent into this using `parentWorld`. +With that, all that's left to get the correct world transform of *this* node is +to combine that with its own local transform. We don't have to walk *up* the +parent chain to calculate world transforms because we calculate as we go while +walking *down* the chain. + +我们使用`parentWorld`将父节点的世界转换传入节点。通过这样,需要获得*这个*节点的世界转换只需要将其和节点本身转换相结合。我们不需要*向上*遍历父节点去计算世界转换,因为我们可以在*向下*遍历时计算。 + +We calculate the node's world transform and store it in `world`, then we render +the mesh, if we have one. Finally, we recurse into the child nodes, passing in +*this* node's world transform. All in all, it's a tight, simple recursive +method. + +我们计算了节点的世界转换然后将其存储到`world`,然后我们渲染网格,如果有的话。最后我们遍历进入子节点,传入*这个*节点的世界转换。无论如何,这是一个紧密的,简单的遍历方法。 + +To draw an entire scene graph, we kick off the process at the root node: + +为了绘制整个场景图,我们从根节点开始整个过程。 + +^code render-root + +### Let's get dirty + +### 让我们使用脏 + +So this code does the right thing -- it renders all the meshes in the right place +-- but it doesn't do it efficiently. It's calling `local_.combine(parentWorld)` +on every node in the graph, every frame. Let's see how this pattern fixes that. +First, we need to add two fields to `GraphNode`: + +所以代码做了正确的事情——他在正确的地方渲染正确的网格——但是他没有有效的的完成。他在途中的每一个节点每帧调用`local_.combine(parentWorld)`。让我们看看这个模式是如何修复这一点的。首先,我们给`GraphNode`添加两个字段。 + +^code dirty-graph-node + +The `world_` field caches the previously calculated world transform, and +`dirty_`, of course, is the dirty flag. Note that the flag starts out `true`. +When we create a new node, we haven't calculated its world transform yet. At +birth, it's already out of sync with the local transform. + +`world_`字段缓存了前一个计算出来的世界转换,和`dirty_`字段,当然,是脏标识。注意标识开始的时候是`true`。当我们创建一个新节点的时候,我们还没有计算出它的世界转换。在开始时,他与自身转换是同步的。 + +The only reason we need this pattern is because objects can *move*, so let's add +support for that: + +我们需要这个模式的唯一原因是对象可以*移动*,因此让我们添加这一点的支持: + +^code set-transform + +The important part here is that it sets the dirty flag too. Are we forgetting +anything? Right -- the child nodes! + +这里重要的部分是设置脏标识。我们忘了什么事吗?是的——子节点! + +When a parent node moves, all of its children's world coordinates are +invalidated too. But here, we aren't setting their dirty flags. We *could* do +that, but that's recursive and slow. Instead, we'll do something clever when we +go to render. Let's see: + +当父节点移动时,所有它自己的的世界坐标也改变了。但是这里,我们还没有设置他们的脏标识。我们*可以*这样做,但这样做是递归而且缓慢。我们可以在渲染时做点更聪明的事情。让我们看看: + + + +^code dirty-render + + + +This is similar to the original naïve implementation. The key changes are that +we check to see if the node is dirty before calculating the world transform and +we store the result in a field instead of a local variable. When the node is +clean, we skip `combine()` completely and use the old-but-still-correct `world_` +value. + +这与原先的实现很相似。光剑改变是我们在计算他的世界转换之前去检查节点是不是脏的,然后将结果存在字段中而不是本地变量。如果节点是干净的,我们完全跳过了`combine()`使用了老的但是正确的`world_`值。 + +The clever bit is that `dirty` parameter. That will +be `true` if any node above this node in the parent chain was dirty. In much the +same way that `parentWorld` updates the world transform incrementally as we +traverse down the hierarchy, `dirty` tracks the dirtiness of the parent chain. + +这个聪明的位是那个`dirty`参数。如果任何他父节点练上有任何是脏的,那么这个就是`true`。当我们顺着层次遍历下来,`parentWorld`用同样的方式更新他的世界转换,`dirty`标识了父节点链的脏。 + +This lets us avoid having to recursively mark each child's `dirty_` flag +in `setTransform()`. Instead, we pass the parent's dirty flag down to its +children when we render and look at that too to see if we need to recalculate +the world transform. + +这让我们避免递归地调用`setTransform()`标注每一个子节点的`dirty_`标识。相反,我们将父节点的脏标识传递给他们的子节点,然后看看我们是否需要重新计算它的世界转换。 + +The end result here is exactly what we want: changing a node's local transform +is just a couple of assignments, and rendering the world calculates the exact +minimum number of world transforms that have changed since the last frame. + +这里结果正是我们系那个要的:改变节点的自身转换只是一些什么,渲染世界的时候只计算从上一帧开始所需的最小数量的世界转换。 + + + +## Design Decisions + +## 设计决策 + +This pattern is fairly specific, so there are only a couple of knobs to twiddle: + +这种模式非常具体的,所以只有几个旋钮来摆弄: + +### When is the dirty flag cleaned? + +### 什么时候清空脏标识? + +* **When the result is needed:** + +* *当结果被请求时?* + + * *It avoids doing calculation entirely if the result is never used.* For + primary data that changes much more frequently than the derived data is + accessed, this can be a big win. + + * *如果不需要结果,可以完全避免计算。*如果原始数据变化的速度比推导数据获取的速度快的多,这可以是很大的胜利。 + + * *If the calculation is time-consuming, it can cause a noticeable pause.* + Postponing the work until the player is expecting to see the result can + affect their gameplay experience. It's often fast enough that this + isn't a problem, but if it is, you'll have to do the work earlier. + + * *如果计算是消耗大量时间,这可以造成一个可察觉的暂停。*将工作推迟到玩家想要结果的时候会严重影响他们的游戏体验。这部分工作一般足够快,不会构成问题,但是如果是问题,你就需要早一些做这些工作。 + +* **At well-defined checkpoints:** + +* *在好好定义的检查点处:* + + Sometimes, there is a point in time or in the progression of the game where it's + natural to do the deferred processing. For example, + we may want to save the game only when the pirate sails into port. Or the + sync point may not be part of the game mechanics. We may just want to hide the + work behind a loading screen or a cut scene. + + 有时候,有一个时间点或者在游戏的过程中很自然的需要推迟处理。例如,我们只有海盗驶入港口才会去保存游戏。或者同步点不是游戏的机制,我们的将这些工作隐藏在加载画面或者过程动画之后。 + + * *Doing the work doesn't impact the user experience.* Unlike the previous + option, you can often give something to + distract the player while the game is busy processing. + + * *做这种工作不会影响到玩家体验。*不像前一个选项,游戏在紧张运行的时候,你总能转移玩家的注意力。 + + * *You lose control over when the work happens.* This is sort of the + opposite of the earlier point. You have micro-scale control over when you + process, and can make sure the game handles it gracefully. + + * *你会丧失何时工作的控制权。*这将工作提前的反面。你在进行的时候有微观控制,能确保有效优雅的处理它。 + + What you *can't* do is ensure the player actually makes it to the + checkpoint or meets whatever criteria you've defined. If they get lost + or the game gets in a weird state, you can end up deferring + longer than you expect. + + 你*不能*做的是保证玩家真的到了检查点或者满足了你定义的条件。如果他们在游戏中迷失了,或者游戏进入了一个奇怪的状态,你最终会将工作推迟到超过你预料的晚。 + +* **In the background:** + +* *在后台处理:* + + Usually, you start a fixed timer + on the first modification and then process all of the changes that happen + between then and when the timer fires. + + 通常情况下,你为每个更改启动一个固定的计时器,然后在计时器开始计时后处理所哟丶变化。 + + + + * *You can tune how often the work is performed.* By adjusting the timer + interval, you can ensure it happens as frequently (or infrequently) as + you want. + + * *你可以控制工作进行的多么频繁。*通过调节计时器,你可以保证他处理的像你预期一样频繁(或者不频繁)。 + + * *You can do more redundant work.* If the primary state only changes a + tiny amount during the timer's run, you can end up processing a large + chunk of mostly unchanged data. + + * *你会做更多冗余工作。*如果原始状态在计时器运行之间只改变了很少的部分,最终你会处理的大部分都是没有改变的数据。 + + * *You need support for doing work asynchronously.* + Processing the data "in the background" implies that the player can + keep doing whatever it is that they're doing at the same time. That + means you'll likely need threading or some other kind of concurrency + support so that the game can work on the data while it's still + being played. + + Since the player is likely interacting with + the same primary state that you're processing, you'll need to think + about making that safe for concurrent modification too. + + * *你需啊哟同步支持工作。*在“后台”处理数据意味着玩家可以同时继续做他们正在做的事情。这就意味着你将会需要线程或者其他并行支持,这样游戏在处理数据的同时仍然可以保持游玩。 + +### How fine-grained is your dirty tracking? + +### 你的脏追踪做的有多细粒度? + +Imagine our pirate game lets players build and customize their pirate ship. +Ships are automatically saved online so the player can resume where they left +off. We're using dirty flags to determine which decks of the ship have been +fitted and need to be sent to the server. Each chunk of data we send to the +server contains some modified ship data and a bit of metadata describing where +on the ship this modification occurred. + +* **If it's more fine-grained:** + +* *用越细的粒度:* + + Say you slap a dirty flag on each tiny plank of each deck. + + 假设你为甲板上的每一个小木板都拍上一个脏标识。 + + * *You only process data that actually changed.* You'll send exactly the + facets of the ship that were modified to the server. + + * *你只需处理真正改变的数据。*你只处理修改了船舶方面的数据到服务器。 + +* **If it's more coarse-grained:** + +* *如果粒度更粗:* + + Alternatively, we could associate a dirty bit with each deck. + Changing anything on it marks the entire deck dirty. + + 或者,我们可以每层甲板关联一个脏标识。改变它上面的任何东西都会让整个甲板变脏。 + + + + * *You end up processing unchanged data.* Add a single barrel to a deck + and you'll have to send the whole thing to the server. + + * *你最终需要处理没有变化的数据。*在甲板上添加一个桶,就要将全部的东西发送到服务器。 + + * *Less memory is used for storing dirty flags.* Add ten barrels to a deck + and you only need a single bit to track them all. + + * *更少的内存被用在存储脏标识上。*为甲板上添加十个桶只需要一位来追踪他们。 + + * *Less time is spent on fixed overhead.* When processing some changed data, + there's often a bit of fixed work you have to do on top of handling the + data itself. In the example here, that's the metadata required to + identify where on the ship the changed data is. The bigger your + processing chunks, the fewer of them there are, which means the less + overhead you have. + + * *较少的时间花在固定开销上。*当处理某些改变了的数据时,通常需要处理顶部的数据本身。在这个例子中,有些元数据需要确认船上改变的数据在哪里。你处理的块越大,那么要处理的数量就越少,这就意味着有更小的开销。 + +## See Also + +## 参见 + + * This pattern is common outside of games in browser-side web frameworks like + [Angular](http://angularjs.org/). They use dirty flags to track which data + has been changed in the browser and needs to be pushed up to the server. + + * 这个模式在游戏之外的领域比如浏览器方向的框架是很常见的。他们使用脏标识来追踪哪个数据在浏览器中被改变了,需要被推向服务器。 + + * Physics engines track which objects are in motion and which are resting. + Since a resting body won't move until an impulse is applied to it, they + don't need processing until they get touched. This "is moving" bit is a + dirty flag to note which objects have had forces applied and need to have + their physics resolved. + + * 物理引擎最终哪些对那些对象在运动中哪些在休息,由于休息的骨骼知道有力施加在上面才会移动,他们知道被碰到才会需要处理。这种“正在移动”的位是一个脏标识来标注那一个对象上面有力施加,需要他们的物理解析。 \ No newline at end of file diff --git a/book/double-buffer.markdown b/book/double-buffer.markdown new file mode 100644 index 0000000..2022466 --- /dev/null +++ b/book/double-buffer.markdown @@ -0,0 +1,759 @@ +^title Double Buffer +^section Sequencing Patterns + +## Intent + +## 意图 + +*Cause a series of sequential operations to appear instantaneous or +simultaneous.* + +*瞬间或者同时引发一序列的操作。* + +## Motivation + +## 动机 + +In their hearts, computers are sequential beasts. +Their power comes from being able to break down the largest tasks into tiny +steps that can be performed one after another. Often, though, our users need to +see things occur in a single instantaneous step or see multiple tasks performed +simultaneously. + +在他们心中,电脑是一个连续运行的野兽。他们的力量来自于将大的人物分解为小的步骤,这样可以一个接一个完成。但是,通常我们用户需要看到事情在一个瞬间发生事情或者让多个任务同时进行。 + + + +A typical example, and one that every game engine must address, is rendering. +When the game draws the world the users see, it does so one piece at a time -- the +mountains in the distance, the rolling hills, the trees, each in its turn. If +the user *watched* the view draw incrementally like that, the illusion of a +coherent world would be shattered. The scene must update smoothly and quickly, +displaying a series of complete frames, each appearing instantly. + +一个典型的例子,也是一个每个游戏引擎都得掌控的问题,渲染。但游戏渲染玩家见到的世界时,他同时处理了一大块——远处的山,起伏的丘陵,树木,每一种都在各自的循环中。如果在用户的*观察*到增量做这些,连续世界的幻觉就会被打破。场景必须快速流畅的更新,显示一系列完整的帧,每一个都是立即出现。 + +Double buffering solves this problem, but to understand how, we first need to +review how a computer displays graphics. + +双重缓存解决了这个问题,但是为了理解如何去做,我们首先的复习计算机是如何显示图形的。 + +### How computer graphics work (briefly) + +### 电脑图形是如何工作的(简短介绍) + +A video display like a computer monitor draws one pixel +at a time. It sweeps across each row of pixels from left to right and then +moves down to the next row. When it reaches the bottom right corner, it scans +back up to the top left and starts all over again. It does this so fast -- +around sixty times a second -- that our eyes can't see the scanning. To us, it's +a single static field of colored pixels -- an image. + +在电脑屏幕上显示视频是一次绘制一个像素点。它从左到右扫描每一行像素点,然后移动至下一行。当他抵达了右下角,它退回左上角重新开始。他做的飞快——每秒六十次——我们的眼睛看不出扫描。对我们来说,这是一整张固定的有色像素——一张图像。 + + + +You can think of this process like a tiny hose that pipes pixels to the display. +Individual colors go into the back of the hose, and it sprays them out across +the display, one bit of color to each pixel in its turn. So how does the hose +know what colors go where? + +你可以将整个过程想象为一个软管向屏幕喷洒像素。独特的像素从软管的后面流入,然后在屏幕上喷洒,每次对一个像素涂一位的颜色。所以软管怎么知道那种颜色要喷到哪里去? + +In most computers, the answer is that it pulls them from a *framebuffer*. A +framebuffer is an array of pixels in memory, a chunk of RAM where each couple of +bytes represents the color of a single pixel. As the +hose sprays across the display, it reads in the color values from this array, +one byte at a time. + +在大多数电脑上,答案是从*帧缓冲*中取出他们。一个帧缓冲是内存中的色素数值,RAM的一块每一字节表示一个像素的颜色。当软管喷洒屏幕时,他从这个数组中读取颜色值,一次一个字节。 + + + +Ultimately, in order to get our game to appear on screen, all we do is write to +that array. All of the crazy advanced graphics algorithms we have boil down to +just that: setting byte values in the framebuffer. But there's a little problem. + +最终为了让我们的游戏在屏幕中显示,我们需要写到这个数组中去。我们使用的疯狂图形算法最终都到了这里:设置帧缓冲中的字节值。但这里有个小问题。 + +Earlier, I said computers are sequential. If the machine is executing a chunk of +our rendering code, we don't expect it to be doing anything else at the same +time. That's mostly accurate, but a couple of things *do* happen in the middle +of our program running. One of those is that the video display will be reading +from the framebuffer *constantly* while our game runs. This can cause a problem +for us. + +早先,我说过计算机是顺序的。如果机器在运行一块我们的渲染代码,我们不期望它同时还能做些别的什么事情。这通常是很准确的,但是有些事情*确实*在我们程序运行中发生。其中一件事是,当游戏运行时,视频输出正在*不断*从帧缓冲中读取数据。这可能会对我们造成问题。 + +Let's say we want a happy face to appear on screen. Our program starts looping +through the framebuffer, coloring pixels. What we don't realize is that the +video driver is pulling from the framebuffer right as we're writing to it. As it +scans across the pixels we've written, our face starts to appear, but then it +outpaces us and moves into pixels we haven't written yet. The result is +*tearing*, a hideous visual bug where you see half of something drawn on screen. + +假设我们要在屏幕上显示一张笑脸。我们的程序在帧缓冲上开始循环为像素点涂色。我们没有意识到的是视频驱动在我们写入的同时正在读取它。当他扫描过真个我们写的像素时,我们的笑脸正在开始出现,但是之后他进入了我们没有写的部分,将没有写的像素绘制到了屏幕上。结果就是*哭泣*,让你在屏幕上看到绘制了一半的图像,这是一个可见的可怕漏洞。 + + + +A series of images of an in-progress frame being rendered. A pointer writes pixels while another reads them. The reader outpaces the writer until it starts reading pixels that haven't been rendered yet. + + + +This is why we need this pattern. Our program renders the pixels one at a time, +but we need the display driver to see them all at once -- in one frame the face +isn't there, and in the next one it is. Double buffering solves this. I'll explain how +by analogy. + +这就是为什么我们需要这个模式。我们的撤销一次渲染一个像素,但是我们需要显示驱动一次全部看到他们——在一帧中没有出现脸,下一帧就出现了。双重缓冲解决了这个。我会用类比来解释。 + +### Act 1, Scene 1 + +### 表演1,场景1 + +Imagine our users are watching a play produced by ourselves. As scene one ends +and scene two starts, we need to change the stage setting. If we have the +stagehands run on after the scene and start dragging props around, the illusion +of a coherent place will be broken. We could dim the lights while we do that +(which, of course, is what real theaters do), but the audience still knows +*something* is going on. We want there to be no gap in time between scenes. + +想象我的玩家正在看我们直接演出的表演。在场景一结束而场景二开始时,我们需要改变舞台设置。如果我们让场务在场景结束后进去拖动东西,在连贯地点演出的幻觉就被打破了。我们可以减弱灯光(这当然是剧院真正做的),但是观众还是知道*有什么*在进行,我们想在场景间毫无跳跃的转换。 + +With a bit of real estate, we come up with this clever solution: we build +*two* stages set up so the audience can see both. Each +has its own set of lights. We'll call them stage A and stage B. Scene one is shown on +stage A. Meanwhile, stage B is dark as the stagehands are setting up scene two. As soon as scene one ends, we cut the lights on stage A and bring them +up on stage B. The audience looks to the new stage and scene two begins +immediately. + +通过房地产,我们想到了这样一个聪明的解决方案:我们建*两个*舞台,观众两个都能看到。每一个有它直接的一组灯光。我们称这些舞台为舞台A和舞台B。场景一在舞台A上。同时在场务设置场景二的时候,舞台B是黑的。当场景一完成后,我们切断了场景A的灯光,然后将它们带到场景B。广中看向新舞台,场景二立即开始。 + +At the same time, our stagehands are over on the now darkened stage *A*, +striking scene one and setting up scene *three*. As soon as scene two ends, we +switch the lights back to stage A again. We continue this process for the entire +play, using the darkened stage as a work area where we can set up the next +scene. Every scene transition, we just toggle the lights between the two stages. +Our audience gets a continuous performance with no delay between scenes. They +never see a stagehand. + +同时,我们的场务到了黑咕隆咚的舞台*A*,收拾了场景一然后设置场景*三*。一旦场景二结束了,我们将灯光转回舞台A。我们在整场表演中进行这样的活动,使用黑暗的舞台作为设置下一个场景的工作区域。每一次场景转换,我们只是在两个舞台间切换灯光。我们的观众获得了连续的体验,没有在场景间感到延迟。他们从来没有见到一个场务。 + + + +### Back to the graphics + +### 重新回到图形 + +That is exactly how double buffering works, and this +process underlies the rendering system of just about every game you've ever +seen. Instead of a single framebuffer, we have *two*. One of them represents the +current frame, stage A in our analogy. It's the one the video hardware is +reading from. The GPU can scan through it as much as it wants whenever it wants. + +这就是双重缓冲是如何工作的,这就是你看到的几乎每一个游戏背后的渲染系统。不是使用一个帧缓冲,我们使用*两个*。其中一个堤岸边现在的帧,我们类比中的舞台A。是哪个图形硬件渲染的那一个。GPU可以想什么时候扫就什么时候扫。 + + + +Meanwhile, our rendering code is writing to the *other* framebuffer. This is our +darkened stage B. When our rendering code is done drawing the scene, it switches +the lights by *swapping* the buffers. This tells the video hardware to start +reading from the second buffer now instead of the first one. As long as it times +that switch at the end of a refresh, we won't get any tearing, and the entire +scene will appear all at once. + +同时,我们的渲染代码正在写入*另一个*帧缓冲。这是我们黑暗中的舞台B。当我们的渲染代码完成了描绘场景,它将通过*交换*缓存来改变灯光。这告诉图形硬件开始从第二块缓存中读取而不是第一块。只要在更新的最后时刻转换,我们就不会有任何悲伤,整个场景都会一下子出现。 + +Meanwhile, the old framebuffer is now available for use. We start rendering the +next frame onto it. Voilà! + +同时,以前的帧缓冲现在可以使用了。我们可以将下一帧渲染在它上面了。Voil! + +## The Pattern + +## 模式 + +A **buffered class** encapsulates a **buffer**: a piece of state that can be +modified. This buffer is edited incrementally, but we want all outside code to +see the edit as a single atomic change. To do this, the class keeps *two* +instances of the buffer: a **next buffer** and a **current buffer**. + +一个*双缓冲类*封装了一个**缓冲**:一段你可以改变的状态。这个缓冲被增量的修改,但是我们想要外部的代码将其视为一个单一的原子改变。为了达到这一点,类保存了*两个*缓冲的实例:**下一个缓存**和**现在的缓存**。 + +When information is read *from* a buffer, it is always from the *current* +buffer. When information is written *to* a buffer, it occurs on the *next* +buffer. When the changes are complete, a **swap** operation swaps the next and +current buffers instantly so that the new buffer is now publicly visible. The +old current buffer is now available to be reused as the new next buffer. + +当信息*从*一个缓存中读取,他总是读取*现在的*缓存。当信息需要写*到*一个缓存,他总是在*下一*缓存上操作。当改变完成后,一个**交换**炒作会立刻将现在的缓存和下一个缓存交换,这样新的缓存就是公众可见的了。以前的现在缓存被视为新的下一个缓存重用。 + +## When to Use It + +## 何时使用 + +This pattern is one of those ones where you'll know when you need it. If you +have a system that lacks double buffering, it will probably look visibly wrong +(tearing, etc.) or will behave incorrectly. But saying, "you'll know when you +need it" doesn't give you much to go on. More specifically, this pattern is +appropriate when all of these are true: + +这个模式是那种你需要它你就会知道的类型。如果你有一个系统需要双重缓存,它可能有可见的错误(泪水之类的)或者行为不正确。但是,“当你需要时你会知道的”并不会给你太多继续下去的东西。更加特殊的,当以下情况都满足的时候,使用这个模式就是很恰当的了: + + * We have some state that is being modified incrementally. + + * 我们需要增量修改一些状态。 + + * That same state may be accessed in the middle of modification. + + * 在修改之中,这个状态可能会被外部请求。 + + * We want to prevent the code that's accessing the state from + seeing the work in progress. + + * 我们想要请求状态的外部代码知道里面是如何工作的。 + + * We want to be able to read the state and we don't want to have to + wait while it's being written. + + * 我们想要读取状态,而且不需要在修改的时候等待。 + +## Keep in Mind + +## 记住 + +Unlike larger architectural patterns, double buffering exists at a lower +implementation level. Because of this, it has fewer consequences for the rest of +the codebase -- most of the game won't even be aware of the difference. There +are a couple of caveats, though. + +不像更大的架构模式,双缓冲模式在一个底层实现级别。因为如此,他对代码库的其他部分有更少的影响——大多数游戏甚至不会感到有区别。虽然这里还是有几个警告。 + +### The swap itself takes time + +### 交换本身需要时间 + +Double-buffering requires a *swap* step once the state is done being modified. +That operation must be atomic -- no code can access *either* state while they +are being swapped. Often, this is as quick as assigning a pointer, but if it +takes longer to swap than it does to modify the state to begin with, then we haven't +helped ourselves at all. + +在状态被修改后,双缓冲需要一个*swap*步奏。这个操作必须是原子的——在它们交换时,没有代码可以接触到*任何一个*状态。通常,这个修改一个指针那么快,但是如果交换消耗的时间长于修改状态的时间,那我们一点也没有帮到自己。 + +### We have to have two buffers + +### 我们得保留两个缓存 + +The other consequence of this pattern is increased memory usage. As its name implies, the +pattern requires you to keep *two* copies of your state in memory at all times. +On memory-constrained devices, this can be a heavy price to pay. If you can't +afford two buffers, you may have to look into other ways to ensure your state +isn't being accessed during modification. + +这个模式的另一个结果是增加了内存的使用。就像名字暗示的一样,这个模式需要你一直保留*两个*状态的拷贝在内存中。在内存攸关订单设备上,这可能要付出惨痛的代价。乳沟你不能承受使用两个缓存,你一下需要使用别的方法保证状态在修改时不会被请求。 + +## Sample Code + +## 示例代码 + +Now that we've got the theory, let's see how it works in practice. We'll write a +very bare-bones graphics system that lets us draw pixels on a framebuffer. In +most consoles and PCs, the video driver provides this low-level part of the +graphics system, but implementing it by hand here will let us see what's going on. +First up is the buffer itself: + +现在我们知道了理论,让我们看看他在实践中如何使用。我们写一个非常基础的图形系统,允许我们在缓冲帧上描绘像素。在大多数主机和电脑上,图形驱动提供了这种底层的图形系统,但是在这里手动实现有助于让我们理解发生什么。首先是缓存本身: + +^code 1 + +It has basic operations for clearing the entire buffer to a default color and +setting the color of an individual pixel. It also has a function, `getPixels()`, +to expose the raw array of memory holding the pixel data. We won't see this in +the example, but the video driver will call that function frequently to stream +memory from the buffer onto the screen. + +它有清空整个缓存成默认的颜色并将其某一像素设置为特定颜色的操作。他也有函数`getPixels()`,将保存像素数据的数组暴露出来。我们不会再例子中看到,实时视频驱动会频繁调用这个函数将缓存中的数据输送到屏幕上。 + +We wrap this raw buffer in a `Scene` class. It's job here is to render something +by making a bunch of `draw()` calls on its buffer: + +我们将整个缓存包裹在一个`Scene`钟。渲染某物需要做的是在这块缓存上调用一系列`draw()`。 + + + +^code 2 + + + +Every frame, the game tells the scene to draw. The scene clears the buffer and +then draws a bunch of pixels, one at a time. It also provides access to the +internal buffer through `getBuffer()` so that the video driver can get to it. + +每一帧,游戏告诉场景去描绘。场景清空缓存然后绘制一堆像素,一个接一个。这也提过了`getBuffer()`获得缓存,这样视频驱动可以接触到它。 + +This seems pretty straightforward, but if we leave it like this, we'll run into +problems. The trouble is that the video driver can call `getPixels()` on the +buffer at *any* point in time, even here: + +这看起来很直观,但是如果我们就这样做,我们会遇到麻烦。问题在于图形驱动可以在*任何*时间调用`getBuffer()`,甚至在这里: + +^code 3 + +When that happens, the user will see the eyes of the face, but the mouth will +disappear for a single frame. In the next frame, it could get interrupted at some +other point. The end result is horribly flickering graphics. We'll fix this with +double buffering: + +当那发生时,用户就会看到脸的眼睛,但是一帧中嘴就会消失了。在下一帧,又会在某些地方冲突。最终的结果就是可怕闪烁的图形。我们会用双重缓存修复这一点: + +^code 4 + +Now `Scene` has two buffers, stored in the `buffers_` array. We don't directly +reference them from the array. Instead, there are two members, `next_` and +`current_`, that point into the array. When we draw, we draw onto the next +buffer, referenced by `next_`. When the video driver needs to get the pixels, +it always accesses the *other* buffer through `current_`. + +现在`Scene`有两个缓存了,存储在`buffers_`数组中。我们不会直接从数组引用他们。相反,这里会有两个成员,`next_`和`current_`,指向这个数组。当我们描绘时,我们绘制`next_`引用的下一个缓存上。当图形驱动需要获得像素时,他总是通过`current_`获取*另一个*缓存。 + +This way, the video driver never sees the buffer that we're working on. The only +remaining piece of the puzzle is the call to `swap()` when the scene is done +drawing the frame. That swaps the two buffers by simply switching the `next_` +and `current_` references. The next time the video driver calls `getBuffer()`, +it will get the new buffer we just finished drawing and put our recently +drawn buffer on screen. No more tearing or unsightly glitches. + +通过这种方式,视频驱动永远看不到我们正在工作的缓存。谜题的最后一片碎片就是在场景完成绘制一帧的时候调用`swap()`。这通过交换`next_`和`current_`的引用完成这一点。下一次视频驱动调用getBuffer()`,他会获得我们刚刚完成描绘的新缓存,然后将刚刚描绘好的缓存放在屏幕上。没有流泪或者可以看到的漏洞。 + +### Not just for graphics + +### 不仅仅是图形 + +The core problem that double buffering solves is state being accessed while it's +being modified. There are two common causes of this. We've covered the first one +with our graphics example -- the state is directly accessed from code on another +thread or interrupt. + +双缓冲解决的核心问题是状态被修改的同时被请求。这通常有两种原因。我们用我们的图形例子覆盖了第一种——状态被另一个线程的代码或中断请求。 + +There is another equally common cause, though: when the code *doing the +modification* is accessing the same state that it's modifying. This can manifest +in a variety of places, especially physics and AI where you have entities +interacting with each other. Double-buffering is often helpful here too. + +但是,还有一个同样常见的原因:当*负责修改的*代码请求真正修改的同样的状态。这可能发生在很多实体中,特别是物理和AI中,你有实体相互交换时。双缓冲在哪里也十分有用。 + +### Artificial unintelligence + +### 人工不智能 + +Let's say we're building the behavioral system for, of all things, a game based +on slapstick comedy. The game has a stage containing a bunch of actors that run +around and get up to various hijinks and shenanigans. Here's our base actor: + +假设我们正在为一个基于趣味喜剧的游戏构建行为系统。这个游戏包括一堆跑来跑去寻欢作乐恶作剧的角色。这里是我们的基础角色: + +^code 5 + +Every frame, the game is responsible for calling `update()` on the actor so that it has a chance to do some +processing. Critically, from the user's perspective, *all actors should appear +to update simultaneously*. + +每一帧,游戏要在角色身上调用`update()`这样它可以去做些事情。特别的,从玩家的角度*所有的角色都应该看上去同时更新*。 + + + +Actors can also interact with each other, if by "interacting", we mean "they can +slap each other around". When updating, the actor can call `slap()` on another +actor to slap it and call `wasSlapped()` to determine if it has been slapped. + +角色也可以相互交互,通过使用“交互”,我指“他们可以互相扇对方巴掌”。当更新时,角色可以再另一个角色身上调用`slap()`来扇他一巴掌,然后调用`wasSlapped()`决定它是不是被扇了。 + +The actors need a stage where they can interact, so let's build that: + +角色需要一个我们可以交互的状态,所以我们来设置它: + +^code 6 + +`Stage` lets us add actors, and provides a single `update()` call that updates each +actor. To the user, actors appear to move simultaneously, but internally, +they are updated one at a time. + +`Stage`让我们增加角色,然后提供我们一个简单的`update()`调用来更新么一个角色。对于用户,角色看起来是同时移动的,但是实际上,他们是依次更新的。 + +The only other point to note is that each actor's "slapped" state is cleared +immediately after updating. This is so that an actor only responds to a given +slap once. + +这里需要注意的另一点是每一个角色的“被扇”状态在更新后就立刻被清除。这是为什么一个角色对一巴掌只反应一次。 + +To get things going, let's define a concrete actor subclass. Our comedian here +is pretty simple. He faces a single actor. Whenever he gets slapped -- by +anyone -- he responds by slapping the actor he faces. + +为了让事情,让我们定义一个实际角色的子类。我们这里的喜剧演员很简单。他面向一个角色。当他被扇的时候——无论是谁扇的——他山面前的人一巴掌作为回应。 + +^code 7 + +Now, let's throw some comedians on a stage and see what happens. We'll set up +three comedians, each facing the next. The last one will face the first, in a +big circle: + +现在我们把一些喜剧演员丢到舞台上看看发生了什么。我们设置三个演员,每一个面朝另一个,最后一个面朝第一个,形成一个环状: + +^code 8 + +The resulting stage is set up as shown in the following image. The arrows +show who the actors are facing, and the numbers show their index in the stage's array. + +最终舞台被像下面图像上那样设置。箭头描述了角色朝向谁,然后数字显示他们在舞台数组中的索引。 + +Boxes for Harry, Baldy, and Chump, in that order. Harry has an arrow pointing to Baldy, who has an arrow pointing to Chump, who has an arrow pointing back to Harry. + +We'll slap Harry to get things going and see what happens when we start +processing: + +我们扇哈利一巴掌让时间开始然后看看我们处理开始后会发生什么: + +^code 9 + +Remember that the `update()` function in `Stage` updates each actor in turn, so if +we step through the code, we'll find that the following occurs: + +记住`Stage`中的`update()`函数在每一轮中更新每一个角色,因此如果我们检视整个代码,我们会发现以下事件发生: + + Stage updates actor 0 (Harry) + Harry was slapped, so he slaps Baldy + Stage updates actor 1 (Baldy) + Baldy was slapped, so he slaps Chump + Stage updates actor 2 (Chump) + Chump was slapped, so he slaps Harry + Stage update ends + +In a single frame, our initial slap on Harry has propagated through all of +the comedians. Now, to mix things up a bit, let's say we reorder the comedians +within the stage's array but leave them facing each other the same way. + +在单独的一帧中,我们的初始给哈利的一巴掌传给了所有的喜剧演员。现在,将事物混合起来一点,让我们重新排列舞台数组中角色的排序,但是让他们继续保持面向对方的方式。 + +The same boxes as before with the same arrows, but now they are ordered Chump, Baldy, Harry. + +We'll leave the rest of the stage setup alone, but we'll replace the chunk of code +where we add the actors to the stage with this: + +我们不管剩下的舞台,将添加角色到舞台的代码块改为如下: + +^code 10 + +Let's see what happens when we run our experiment again: + +让我们看看当我们再一次运行时样的时候会发生什么: + + Stage updates actor 0 (Chump) + Chump was not slapped, so he does nothing + Stage updates actor 1 (Baldy) + Baldy was not slapped, so he does nothing + Stage updates actor 2 (Harry) + Harry was slapped, so he slaps Baldy + Stage update ends + +Uh, oh. Totally different. The problem is straightforward. When we +update the actors, we modify their "slapped" states, the exact same +state we also *read* during the update. Because of this, changes to +that state early in the update affect later parts of that *same* +update step. + +哦偶。完全不一样了。问题很直观。当我们更新角色的时候,我们修改了他们的“被扇”状态,这也是我们在更新时*读取*状态。因为这样,在早先的更新中修改状态可能会影响之后更新*同一*状态的步骤。 + + + +The ultimate result is that an actor may respond to being slapped in +either the *same* frame as the slap or in the *next* frame based +entirely on how the two actors happen to be ordered on the stage. This +violates our requirement that actors need to appear to run in +parallel -- the order that they update within a single frame shouldn't +matter. + +最终的结果是,一个橘色响应被扇可能是在被扇的*同一*帧或者*下一*帧,这完全基于两个角色在舞台上是如何排序的。这违背了我们需要橘色同时出现的需求——他们在同一帧中更新的顺序不应该对结果有影响。 + +### Buffered slaps + +### 缓存巴掌 + +Fortunately, our Double Buffer pattern can help. This time, instead of +having two copies of a monolithic "buffer" object, we'll be buffering +at a much finer granularity: each actor's "slapped" state: + +幸运的是,我们的双缓冲模式可以帮忙。这一次,不是保存两个整体的“缓冲”,我们会用更好的粒度缓存:每一个角色的“被扇”状态。 + +^code 11 + +Instead of a single `slapped_` state, each actor now has two. Just +like the previous graphics example, the current state is used for +reading, and the next state is used for writing. + +不是使用一个`slapped_`状态,每一个演员现在有两个了。就像我们之前图像的例子一样,现在的状态为读准备,下一状态为写准备。 + +The `reset()` function has been replaced with `swap()`. Now, right +before clearing the swap state, it copies the next state into the +current one, making it the new current state. This also requires a +small change in `Stage`: + +`reset()`函数被替换为`swap()`。现在,就在清除交换状态前,他将下一状态拷贝到现在的里面,让他成为新的现在状态,这也需要在`Stage`中做出小小改变: + +^code 12 + +The `update()` function now updates all of the actors and *then* swaps +all of their states. The end result of this is that an actor will only see a +slap in the frame *after* it was actually slapped. This way, the actors will +behave the same no matter their order in the stage's array. As far as the user +or any outside code can tell, all of the actors update simultaneously within a +frame. + +`update()`函数现在更新所有的角色,*然后*交换他们的状态。最终的结果是角色在实际被扇*之后*的那一帧才能看到巴掌。通过这种方式,角色无论在舞台数组中如何排列都会保持相同的行为。无论外部的代码如何调用,所有的角色在一帧之内同时更新。 + +## Design Decisions + +## 设计决策 + +Double Buffer is pretty straightforward, and the examples we've seen so far +cover most of the variations you're likely to encounter. There are two main +decisions that come up when implementing this pattern. + +双缓冲很直观,我们现在看到了例子也覆盖了大多数逆序你需要的场景。在使用这个模式之前,还有两个主要设计决策需要去做。 + +### How are the buffers swapped? + +### 缓存是如何被交换的? + +The swap operation is the most critical step of the process since we +must lock out all reading and modification of both buffers while it's +occurring. To get the best performance, we want this to happen as +quickly as possible. + +交换操作是整个过程的最重要的一步,以为我们必须在其发生的时候锁住两个缓存上的读取和修改。为了得到最好的性能表现,我们需要这个发生的越快越好。 + + * **Swap pointers or references to the buffer:** + + * **交换缓存的指针或者引用:** + + This is how our graphics example works, and it's the most common + solution for double-buffering graphics. + + 这是我们图形例子工作的范式,这也是大多数双缓冲图形通用的解决方法。 + + * *It's fast.* Regardless of how big the buffer is, the swap is simply a + couple of pointer assignments. It's hard to beat that for speed and + simplicity. + + * *他很快。*不管缓存有多大,交换都只是一对指针作业。很难在速度和简易性上击败他。 + + * *Outside code cannot store persistent pointers to the buffer.* This is + the main limitation. Since we don't actually move the *data*, what we're + essentially doing is periodically telling the rest of the codebase to + look somewhere else for the buffer, like in our original stage analogy. + This means that the rest of the codebase can't store pointers directly + to data within the buffer -- they may be pointing at the wrong one a + moment later. + + * *外部代码不能存储对缓存的永久指针。*这是主要限制。由于我们实际上不能移动*数据*,我们本质上做的是周期性的通知代码库的其他部分到别处去寻找缓存,就像我们前面的舞台类比一样。这就意味着代码库的其他部分不能存储缓存中数据的指针——他们也许指向了一段时间后的错误的那个。 + + This can be particularly troublesome on a system where the video driver + expects the framebuffer to always be at a fixed location in memory. In + that case, we won't be able to use this option. + + 这会严重误导期待缓冲帧永远在内存中的固定地址的视频驱动。在这种情况下,我们不能使用这个选项。 + + * *Existing data on the buffer will be from two frames ago, not the last + frame.* Successive frames are drawn on alternating buffers with no data + copied between them, like so: + + * *在缓存中的数据是两帧之前的,而不是上一帧的。*连续的帧被用候选的帧,无需再在他们之间传递任何数据拷贝,就像这样: + + :::text + Frame 1 drawn on buffer A + Frame 2 drawn on buffer B + Frame 3 drawn on buffer A + ... + + You'll note that when we go to draw the third frame, the data already on + the buffer is from frame *one*, not the more recent second frame. In + most cases, this isn't an issue -- we usually clear the whole + buffer right before drawing. But if we intend to reuse some of the existing data on the buffer, it's + important to take into account that that data will be a frame older than + we might expect. + + 你会注意到当我们绘制第三帧的时候,已经在上面的数据是帧*一*的。而不是最近的帧二的。在大多数情况下,这不是什么问题——我们通常在绘制之前清空整个帧。但是如果我们想沿用某些缓存中已有的数据,考虑数据也许比我们期望的老就值得了。 + + + + * **Copy the data between the buffers:** + + * **在缓存之间拷贝数据:** + + If we can't repoint users to the other buffer, the only other option is + to actually copy the data from the next frame to the current frame. This is + how our slapstick comedians work. In that case, we chose this method because + the state -- a single Boolean flag -- doesn't take any longer to copy than a + pointer to the buffer would. + + 如果我们不能讲用户指向其他缓存,唯一的选项就是将下一帧的数据实实在在的拷贝到现在这一帧上。这是我们的扇巴掌喜剧的工作方法。在那种情况下,我们选择这种方式因为状态——一个简单的布尔标识——不需要比修改一个指向缓存的指针开销更大。 + + * *Data on the next buffer is only a single frame old.* This is the nice + thing about copying the data as opposed to ping-ponging back and forth + between the two buffers. If we need access to previous buffer data, this + will give us more up-to-date data to work with. + + * *下一帧的数据只比之前老一帧。*拷贝数据这点与乒乒乓乓的将缓存指来指去正相反。如果我们需要前一帧的数据,这会给我们更加新的数据来工作。 + + * *Swapping can take more time.* This, of course, is the big negative + point. Our swap operation now means copying the entire buffer in memory. + If the buffer is large, like an entire framebuffer, it can take a + significant chunk of time to do this. Since nothing can read or write to + *either* buffer while this is happening, that's a big limitation. + + * *交换也许很花时间。*这个,当然,是最大的负面。我们的交换炒作现在意味着在内存中拷贝整个缓存。如果缓存很大比如一整个缓冲帧,这需要花费可观的时间。由于此事发生时没有东西可以读取或者写入*每个*缓存,这是很大的限制。 + +### What is the granularity of the buffer? + +### 缓存的粒度如何? + +The other question is how the buffer itself is organized -- is it a single monolithic +chunk of data or distributed among a collection of objects? Our graphics +example uses the former, and the actors use the latter. + +这里一个问题是缓存本身是如何组织的——是一个整体的数据块还是散布在对象集合中?我们的图形例子是前一个,而角色是后一个。 + +Most of the time, the nature of what you're buffering will lead to the answer, +but there's some flexibility. For example, our actors all could have stored +their messages in a single message block that they all reference into by their +index. + +大多数情况下,你缓存的范式自然而然就会引导你找到答案,但是这里有一些灵活度。比如,我们的艳阳重视可以将他们的消息存在一个单独的消息块中他们都使用索引来引用。 + + * **If the buffer is monolithic:** + + * **如果缓存是一整块:** + + *

*Swapping is simpler.* Since there is only one pair of buffers, a + single swap does it. If you can swap by changing pointers, then you can + swap the entire buffer, regardless of size, with just a couple of + assignments.

+ + *

*交换更简单。*友谊这里只有一对缓存,一个简单的交换就完成了。如果你可以改变指针来交换,纳米你就可以交换整个缓存,不必在意大小,只需几个操作。

+ + * **If many objects have a piece of data:** + + * **如果很多对象都有一块数据:** + + * *Swapping is slower.* In order to swap, we need to iterate through the + entire collection of objects and tell each one to swap. + + * *交换更慢。*为了交换,我们需要遍历整个对象集合通知每一个都得交换。 + + In our comedian example, that was OK since we needed to clear the next + slap state anyway -- every piece of buffered state needed to be touched + each frame. If we don't need to otherwise touch the old buffer, there's + a simple optimization we can do to get the same performance of a + monolithic buffer while distributing the buffer across multiple objects. + + 在我们喜剧的例子中,这没问题,因为反正我们需要清除扇巴掌状态——每一块缓存的数据都需要美帧接触。如果我们不需要接触老的那一帧,这有一个简单的操作让我们可以在多个对象间分散状态获得使用整块缓存一样的性能。 + + The idea is to get the "current" and "next" pointer concept and apply it + to each of our objects by turning them into object-relative *offsets*. + Like so: + + 这个主意就是将“现在”和“下一个”指针概念改为对象相关的*偏移量*。就像这样: + + ^code 13 + + Actors access their current slap state by using `current_` to index into + the state array. The next state is always the other index in the array, + so we can calculate that with `next()`. Swapping the state simply + alternates the `current_` index. The clever bit is that `swap()` is now + a *static* function -- it only needs to be called once, and *every* + actor's state will be swapped. + + 角色通过使用`current_`索引到状态数字获得当前的被扇状态,下一个状态总是数组中的另一个索引,这样我们可以用`next()`来计算他。交换状态只需交替`current_`索引。聪明之处在于`swap()`现在是一个*静态*函数,他只需被调用一次,*每一个*角色的状态都会被交换。 + +## See Also + +## 参见 + + *

You can find the Double Buffer pattern in use in almost every graphics + API out there. For example, OpenGL has `swapBuffers()`, Direct3D has "swap + chains", and Microsoft's XNA framework swaps the framebuffers within its + `endDraw()` method.

+ + *

你可以在几乎每一个图形API中找到双缓冲。举个例子,OpenGL有`swapBuffers()`,Direct3D有"swap + chains", Microsoft的XNA框架通过他的`endDraw()`方法。

diff --git a/book/event-queue.markdown b/book/event-queue.markdown new file mode 100644 index 0000000..84be23d --- /dev/null +++ b/book/event-queue.markdown @@ -0,0 +1,1155 @@ +^title Event Queue +^section Decoupling Patterns + +## Intent + +## 意图 + +*Decouple when a message or event is sent from when it is processed.* + +*解耦消息或事件发出的时间和处理它的时间。* + +## Motivation + +## 动机 + +Unless you live under one of the few rocks that still lack Internet access, +you've probably already heard of an "event queue". +If not, maybe "message queue", or "event loop", or "message pump" rings a bell. +To refresh your memory, let's walk through a couple of common manifestations of +the pattern. + +除非你还呆在一两个没有互联网接入的犄角旮旯,你可能以及听说过“事件序列”了。如果灭有,也许“消息队列”或者“事件循环”或者“消息泵”可以让你想起些什么。为了更新你的记忆,让我们了解几个此模式使用的常用证明。 + + + +### GUI event loops + +### GUI事件循环 + +If you've ever done any user interface +programming, then you're well acquainted with *events*. Every time the user +interacts with your program -- clicks a button, pulls down a menu, or presses a +key -- the operating system generates an event. It throws this object at your +app, and your job is to grab it and hook it up to some interesting behavior. + +若干你曾做过任何用户界面编程,你们你就会对*事件*很熟悉。每一个用户与你的程序交互——点击按钮,拉出菜单,或者按个键——操作系统就会生成一个事件。他会将这个对象扔给你的应用,你的工作就是抓住它然后挂上一些有趣的行为。 + + + +In order to receive these missives, somewhere deep in the bowels of your code is +an *event loop*. It looks roughly like this: + +为了受到这些信件,在你代码的深处是一个*事件循环*。它大体上是这样的: + +^code event-loop + +The call to `getNextEvent()` pulls a bit of unprocessed user input into your +app. You route it to an event handler and, like magic, your application comes to +life. The interesting part is that the application *pulls* in the event when +*it* wants it. The operating system doesn't just immediately jump to some code in your app when the user pokes a +peripheral. + +对`getNextEvent()`调用将一堆未处理的用户输出传到你的应用中。你将它导向一个事件处理器,之后,就如魔法一般,你的应用获得了生命。有趣的部分是应用*它*想要的时候*获取*事件。操作系统在用户拔掉一个设备的时候不是直接跳转你应用的某处代码。 + + + +That means when user input comes in, it needs to go somewhere so that the +operating system doesn't lose it between when the device driver reported the +input and when your app gets around to calling `getNextEvent()`. That +"somewhere" is a *queue*. + +这就意味着当用户输入进来时,他需要去某处,这样操作系统在设备驱动报告输入和你应用去调用`getNextEvent()`之间不会丢失它。这个“某处”是一个*队列*。 + +An event queue. The operating system enqueues Shift, Down, Up, and Click events, and the getNextEvent() function dequeues them. + +When user input comes in, the OS adds it to a queue of unprocessed events. When +you call `getNextEvent()`, that pulls the oldest event off the queue and hands it +to your application. + +当用户输入抵达时,系统将其添加到未处理事件的队列中就。当你调用`getNextEvent()`时,他从队列中获取最老的事件然后将其交给你的应用。 + +### Central event bus + +### 中心事件总线 + +Most games aren't event-driven like this, but it +is common for a game to have its own event queue as the backbone of its nervous +system. You'll often hear "central", "global", or "main" used to describe it. +It's used for high level communication between game systems that want to stay +decoupled. + +大多数游戏不是像这样的事件驱动,但是在游戏中使用一个事件循环来支撑他的中枢系统是很常见的。你通常听到“中心”“全局”“主体”被用来描述它。这对于游戏系统想要保持解耦的高层相互交流是很方便的。 + + + +Say your game has a tutorial system to display help +boxes after specific in-game events. For example, the first time the player +vanquishes a foul beastie, you want to show a little balloon that says, "Press X +to grab the loot!" + +假设你的游戏有一个示例系统来在某些特定游戏事件后显示帮助框。举个例子,第一次玩家击败了一个肮脏的动物,你想要展示一个小气泡显示着,“按X拿起战利品!” + + + +Your gameplay and combat code are likely complex enough as it is. The last thing +you want to do is stuff a bunch of checks for triggering tutorials in there. +Instead, you could have a central event queue. Any game system can send to it, +so the combat code can add an "enemy died" event every time you slay a foe. + +你的游戏玩法和战斗代码也许像上面一样复杂。你最后想做的事情是检查一堆示例的触发器。相反,你可以拥有一个中心事件队列。任何游戏系统都可以发给他,所以战斗代码可以在每次你砍掉敌人发布一个“敌人死亡”事件。 + +Likewise, any game system can *receive* events +from the queue. The tutorial engine registers itself with the queue and +indicates it wants to receive "enemy died" events. This way, knowledge of an +enemy dying makes its way from the combat system over to the tutorial engine +without the two being directly aware of each other. + +就像这样,任何游戏系统都能从队列*接受*事件。示例引擎在队列中注册它自己然后表明他想要收到“敌人死亡”事件。用这种方式,一个敌人死了的消息从战斗系统传到了示例引擎,而不需要这两个系统知道对方的存在。 + + + +A central event queue is read from and written to by the Combat and Tutorial code. + +I thought about using this as the example for the rest of the chapter, but I'm +not generally a fan of big global systems. Event queues don't have to be for +communicating across the entire game engine. They can be just as useful within a single class or domain. + +我想过使用这个作为这章其他部分的例子,但是我真的不喜欢这样一个巨大的全局系统。事件队列不需要再整个游戏引擎中沟通。他们在一个类或者域就能很有用了。 + +### Say what? + +### 你说什么? + +So, instead, let's add sound to our game. Humans are mainly visual animals, but +hearing is deeply connected to our emotions and our sense of physical space. The +right simulated echo can make a black screen feel like an enormous cavern, and a +well-timed violin adagio can make your heartstrings hum in sympathetic +resonance. + +所以,相反,让我们给游戏添加一些声音。人类主要是视觉动物,但是听觉深深的连接到我们的情感系统和我们的物理感觉空间。正确模拟的回声可以让一块黑屏幕感觉是一个巨大的洞穴,而一个适时的小提琴慢板可以让你的心弦哼着同样的旋律。 + +To get our game wound for sound, we'll start with the simplest possible approach +and see how it goes. We'll add a little "audio +engine" that has an API for playing a sound given an identifier and a +volume: + +为了获得游戏受伤的声音,我们从最简单的解决方法开始然后看看如何进行。我们会添加一点“声音引擎”拥有一个使用标识符和音量就可以播放音乐的API。 + + + +^code sync-api + +It's responsible for loading the appropriate sound resource, finding an +available channel to play it on, and starting it up. This chapter isn't about +some platform's real audio API, so I'll conjure one up that we can presume is +implemented elsewhere. Using it, we write our method like so: + +他负责加载合适的声音资源,找到一个可靠的频道播放,然后启动它。这一章不是关于摸个平台正是的音频API,所以我会假设我们在其他某处魔术般实现了一个。使用它,我们像这样写了我们的方法: + +^code sync-impl + +We check that in, create a few sound files, and start sprinkling `playSound()` +calls through our codebase like some magical audio fairy. For example, in our UI +code, we play a little bloop when the selected menu item changes: + +我们引用它,加入一些声音文件,然后在我们的代码苦衷像魔法仙女一样调用`playSound()`。举个例子,在我们的UI代码中,我们再选择菜单项变化时播放一点杂音: + +^code menu-bloop + +After doing this, we notice that sometimes when you switch menu items, the whole +screen freezes for a few frames. We've hit our first issue: + +这样做了之后,我们注意到但你改变菜单项目的时候,整个屏幕就会冻住几帧。我们找到了第一个问题: + + * **Problem 1: The API blocks the caller until the audio engine has completely + processed the request.** + + * **问题一:API在音频引擎完成处理请求前阻塞了调用者。** + +Our `playSound()` method is *synchronous* -- it doesn't return back to the +caller until bloops are coming out of the speakers. If a sound file has to be +loaded from disc first, that may take a while. In the meantime, the rest of the +game is frozen. + +我们的`playSound()`方法是*同步*的——他再再赢从播放器放出前不会返回调用者。如果一个声音文件要从光盘上加载,那就得花费一定时间。与此同时,游戏的其他部分被冻住了。 + +Ignoring that for now, we move on. In the AI code, we add a call to let out a +wail of anguish when an enemy takes damage from the player. Nothing warms a +gamer's heart like inflicting simulated pain on a virtual living being. + +现在忽视这一点,我们继续。在AI代码中,我们增加了一个调用,在敌人承受玩家的伤害时发出痛苦的低号。没有什么比在虚拟的生物身上施加痛苦更能温暖玩家心理的了。 + +It works, but sometimes when the hero does a mighty attack, it hits two enemies +in the exact same frame. That causes the game to play the wail sound twice +simultaneously. If you know anything about audio, +you know mixing multiple sounds together sums their waveforms. When those are +the *same* waveform, it's the same as *one* sound played *twice as loud*. It's +jarringly loud. + +他有用,但是有时候玩家做出暴击时,他在同一帧可以打到两个敌人。这引起游戏同时要播放两遍哀嚎。如果你知道任何有关音频的事情,纳米你就知道要把两个不同的声音混合在一起要加起来他们的波形。但这些是*同一个*波形时,它与*一个*声音播放*两遍大*是一样的。这就会刺耳的响。 + + + +We have a related problem in boss fights when piles of minions are running +around causing mayhem. The hardware can only play so many sounds at one time. +When we go over that limit, sounds get ignored or cut off. + +我们在Boss战中有一个相关的问题,当有一堆小怪跑动制造伤害的时候。硬件只能同时播放一定的音频。当我们超过这个限度的时候,声音就被忽视或者切断了。 + +To handle these issues, we need to look at the entire *set* of sound calls to +aggregate and prioritize them. Unfortunately, our audio API handles each +`playSound()` call independently. It sees requests through a pinhole, one at a +time. + +为了处理这些问题,我们需要看到声音调用的整体*集合*,用来整合和排序他们。不幸的是,我们的音频API独立处理每一个`playSound()`调用。这看起来这些请求是从针眼穿过,一次一个。 + + * **Problem 2: Requests cannot be processed in aggregate.** + + * **问题二:请求无法合起来处理。** + +These problems seem like mere annoyances compared to the next issue that falls +in our lap. By now, we've strewn `playSound()` calls throughout the codebase in +lots of different game systems. But our game engine is running on modern +multi-core hardware. To take advantage of those cores, we distribute those +systems on different threads -- rendering on one, AI on another, etc. + +这个问题与下面出现相比似乎只是一个烦恼。现在,我们在很多不同的游戏系统中散布了`playSound()`调用。但是我买单游戏引擎是在现代多核机器上运行的。为了使用这些核带来的优势,我们将这些系统分散在不同线程上——渲染在一个,AI在另一个,诸如此类。 + +Since our API is synchronous, it runs on the *caller's* thread. When we call it +from different game systems, we're hitting our API concurrently from multiple +threads. Look at that sample code. See any thread synchronization? Me neither. + +由于我们的API是同步的,他在*调用者*的线程上运行。当我们从不同的游戏系统调用时,我们从多个线程同时使用API。看看示例代码,看到任何线程同步性了吗?我也没看到。 + +This is particularly egregious because we intended to have a *separate* thread +for audio. It's just sitting there totally idle while these other threads are +busy stepping all over each other and breaking things. + +当我们想要一个*分离的*线程给音频就更加惊人了。他只是傻傻待在那里当其他线程都满了这跟随对方和制造事物。 + + * **Problem 3: Requests are processed on the wrong thread.** + + * **问题三:请求在错误的线程上执行。** + +The common theme to these problems is that the audio engine interprets a call to +`playSound()` to mean, "Drop everything and play the sound right now!" +*Immediacy* is the problem. Other game systems call `playSound()` at *their* +convenience, but not necessarily when it's convenient for the audio engine to +handle that request. To fix that, we'll decouple *receiving* a request from +*processing* it. + + +这个问题的常见主体是音频引擎发出一个调用`playSound()`意味着,“放下任何东西,现在就播放声音!”*立即*就是问题。我们的游戏系统在*他们的*方便时调用`playSound()`,但是不一定音频引擎方便去处理这个请求。为了解决这一点,我们将*接受*请求和*处理*请求解耦。 + +## The Pattern + +## 模式 + +A **queue** stores a series of **notifications or requests** in first-in, +first-out order. Sending a notification **enqueues the request and returns**. +The request processor then **processes items from the queue** at a later time. +Requests can be **handled directly** or **routed to interested parties**. This +**decouples the sender from the receiver** both **statically** and **in time**. + +一个**队列**存储一系列**通知或请求**在先入先出队列中。发送一个通知**将请求放入队列并返回**。请求处理器之后稍晚**从队列中处理事项**。这**解耦了发送者和接受者**,既**静态**又**及时**。 + +## When to Use It + +## 何时使用 + +If you only want to decouple *who* receives a message from its sender, patterns +like Observer and Command +will take care of this with less complexity. You only +need a queue when you want to decouple something *in time*. + +如果你只是想结构*谁*从发送者接受一条消息,模式像观察者模式和命令模式可以用小复杂度来处理它。你只需要一个队列在你需要解耦某些*及时*的东西。 + + + +I think of it in terms of pushing and pulling. You have some code A that wants +another chunk B to do some work. The natural way for A to initiate that is by +*pushing* the request to B. + +我用推和拉来考虑。你有部分代码A需要另一块代码B去做些事情。对A自然的事情是将请求*推*给B。 + +Meanwhile, the natural way for B to process that request is by *pulling* it in +at a convenient time in *its* run cycle. When you have a push model on one end +and a pull model on the other, you need a buffer between them. That's what a +queue provides that simpler decoupling patterns don't. + +同时,对B自然的方式去处理请求是通过在*它自己*方便时将其*拉*入。当你在一个末尾有一个推模型一个拉模型,你需要在他们之间放一个缓存。这就是对鞋提供的二恶简单的解耦模式没有的。 + +Queues give control to the code that pulls from it -- the receiver can delay +processing, aggregate requests, or discard them entirely. But queues do this by +taking control *away* from the sender. All the sender can do is throw a request on the +queue and hope for the best. This makes queues a poor fit when the sender needs +a response. + +队里给了代码拉的控制权——接受者可以延迟处理,合并请求或者完全不理他们。但是队列做了这些事通过将控制权从发送者那里*拿走*完成这一点的。所有的发送者能做的就是想队列发送一个请求然后祈祷。当发送者需要一个回复的时候队列不是一个号选择。 + +## Keep in Mind + +## 记住 + +Unlike some more modest patterns in this book, event queues are complex and tend +to have a wide-reaching effect on the architecture of our games. That means +you'll want to think hard about how -- or if -- you use one. + +不像其他这书中的谦虚的模式,事件队列很复杂,会对游戏架构引起广泛影响。这就意味着你会努力思考如何——或者要不要——使用一个。 + +### A central event queue is a global variable + +### 中心事件队列是一个全局变量 + +One common use of this pattern is for a sort of Grand Central Station that all +parts of the game can route messages through. It's a powerful piece of +infrastructure, but *powerful* doesn't always mean *good*. + +我们通常使用这个模式的方法是一个大的交换站,没给游戏中的部分都能将消息送过这里。这是很有用的基础架构,但是*有用*并不代表*好*。 + +It took a while, but most of us learned the hard way that global variables are +bad. When you have a piece of state that any part of the program can poke at, +all sorts of subtle interdependencies creep in. This pattern wraps that state in +a nice little protocol, but it's still a global, with all of the danger that +entails. + +这会花费一点时间,但是我们大多数艰难的学到了全局变量是不好的。但是有一小片状态,程序的每一部分都能接触到,各种精妙的相关性都会产生。这个模式将状态包裹在一个协议中,但是他还是全局的,仍然有它引发的全部危险。 + +### The state of the world can change under you + +### 世界的状态可以因你改变 + +Say some AI code posts an "entity died" event to a queue when a virtual minion +shuffles off its mortal coil. That event hangs out in the queue for who knows +how many frames until it eventually works its way to the front and gets +processed. + +假设一些AI代码在虚拟小怪结束它一生的时候,将一个“实体死亡”事件发送到队列中。这个事件在读了中等待了谁知道有多少帧后直到他最终到了前面然后得以处理。 + +Meanwhile, the experience system wants to track the heroine's body count and +reward her for her grisly efficiency. It receives each "entity died" event +and determines the kind of entity slain and the difficulty of the kill so it +can dish out an appropriate reward. + +同时,昂贵的系统想要追踪英雄的杀敌数,并奖励他可怕的效率。它接受每一个“实体死亡”事件然后决定他杀死了何种怪物,以及机上的难易程度,最终计算处理合适的奖励值。 + +That requires various pieces of state in the world. We need the entity that died +so we can see how tough it was. We may want to inspect its surroundings to see +what other obstacles or minions were nearby. But if the event isn't received +until later, that stuff may be gone. The entity may have been deallocated, and +other nearby foes may have wandered off. + +他需要游戏世界的多种不同状态。我们需啊哟死亡的实体看看他有多难。我们也许要看看他的周围有什么其他的障碍物或者怪物。但是如果事件没有及时处理,这些东西都会消失。实体可能被清除,周围的东西也有可能移开。 + +When you receive an event, you have to be careful not to assume the *current* +state of the world reflects how the world was *when the event was raised*. This +means queued events tend to be more data heavy than events in synchronous systems. With +the latter, the notification can say "something happened" and the receiver +can look around for the details. With a queue, those ephemeral details must be +captured when the event is sent so they can be used later. + +当你接到一个事件的时候,你的小心,不能假设*现在的*世界转台反映了*当事件发生的时候*的游戏世界。这就意味着队列中的事件比同步系统中的事件需要存储更多数据。在后者中,通知可以说“某事发生了”然后接受者可以寻找细节。通过一个队列,这些短暂的细节必须在事件发送的时候就被捕获以方便之后的使用。 + +### You can get stuck in feedback loops + +### 你看会陷于反馈循环中 + +All event and message systems have to worry about cycles: + +任何事件和消息系统都得担心环路: + + 1. A sends an event. + 2. B receives it and responds by sending an event. + 3. That event happens to be one that A cares about, so it receives it. In + response, it sends an event... + 5. Go to 2. + + 1. A发送了一个事件 + 2. B几首了然后发送了一个事件作为回应。 + 3. 这个事件恰好是A关注的,所以它收到了。为了回应,他发送了一个事件。 + 4. 回到2. + +When your messaging system is *synchronous*, you find cycles quickly -- they +overflow the stack and crash your game. With a queue, the asynchrony unwinds the +stack, so the game may keep running even though spurious events are sloshing back and forth in there. A common rule to avoid this +is to avoid *sending* events from within code that's *handling* one. + +单你的消息系统是*同步的*,你很快就能找到环路——他们溢出了栈并让你的游戏崩溃。使用一个队列,不同步的使用栈,及时在虚假事件晃来晃去的时候,游戏仍然可以继续运行。一个避免这个的通用跪着就是避免在*处理*事件的代码中*发送*事件。 + + + +## Sample Code + +## 示例代码 + +We've already seen some code. It's not perfect, but it has the right basic +functionality -- the public API we want and the right low-level audio calls. All +that's left for us to do now is fix its problems. + +我们已经看到一些代码了。这并不完美,但是有基本的正确功能——公用的API和正确的底层音频调用。剩下我们需要做的就是修复他的问题。 + +The first is that our API *blocks*. When a piece of code plays a sound, it can't +do anything else until `playSound()` finishes loading the resource and actually +starts making the speaker wiggle. + +第一个是我们的API是*阻塞的*。当一块代码播放声音时,他不能做任何其他事情,直到`playSound()`加载完音频然后真正的开始播放。 + +We want to defer that work until later so that `playSound()` can return quickly. +To do that, we need to *reify* the request to play a sound. We need a little +structure that stores the details of a pending request so we can keep it around +until later: + +我们想要将这个工作推迟,这样 `playSound()` 可以很快的返回。为了达到这一点,我们需要*具体化*播放声音的请求。我们需要一个小结构存储发送一个请求的细节,这样我们晚些时候可以使用: + +^code play-message + +Next, we need to give `Audio` some storage space to keep track of these pending +play messages. Now, your algorithms professor might +tell you to use some exciting data structure here like a [Fibonacci +heap](http://en.wikipedia.org/wiki/Fibonacci_heap) or a [skip +list](http://en.wikipedia.org/wiki/Skip_list), or, hell, at least a *linked* +list. But in practice, the best way to store a bunch of homogenous things is +almost always a plain old array: + +下面我们需要个`Audio`一些存储空间来追踪正在进行中的播放消息。现在,你的的算法专家也许会告诉你一写使用吸纳有的数据结构比如Fibonacciheap或者skip list,或者,最起码一个*链接*表。但是在实践中。你存储一堆同类事物最好的办法是使用一个平凡无奇的老数组。 + + + + * No dynamic allocation. + + * 没有动态分配。 + + * No memory overhead for bookkeeping information or pointers. + + * 没有记录信息或者指针的内存天花板。 + + * Cache-friendly contiguous memory usage. + + * 缓存友好的连续空间使用。 + + + +So let's do that: + +所以让我们那样做吧: + +^code pending-array + +We can tune the array size to cover our worst case. To play a sound, we simply +slot a new message in there at the end: + +我们可以将数组大小设置为最糟的丁克。为了播放一个声音,我们简单的将一个新消息插到最后。 + +^code array-play + +This lets `playSound()` return almost instantly, but we do still have to play +the sound, of course. That code needs to go somewhere, and that somewhere is an +`update()` method: + +这让`playSound()`几乎是立即返回,但是我们仍然得播放声音,当然。代码的到别处去,某处就有一个`update()`方法: + + + +^code array-update + + + +Now, we need to call that from somewhere convenient. What "convenient" means +depends on your game. It may mean calling it from the main game loop or from a dedicated audio +thread. + +现在我们需要从方便的某处调用。这个“方便”取决于你的游戏。他也许要从主游戏循环中滴啊用,或者一个专注于音频的线程。 + +This works fine, but it does presume we can process *every* sound request in a +single call to `update()`. If you're doing something like processing a request +asynchronously after its sound resource is loaded, that won't work. For +`update()` to work on one request at a time, it needs to be able to pull +requests out of the buffer while leaving the rest. In other words, we need an +actual queue. + +这可行,但是这假定了我们可以处理*每一个*声音请求在一个对`update()`的调用中。如果你做了一些像在声音资源加载后做了一个异步请求,这就没法工作了。对于`update()`一次处理一个请求,他需要在完成一个之后藏缓存中拉出一个请求的能力。换言之,我们需要一个真实的队列。 + +### A ring buffer + +### 一个环状缓存 + +There are a bunch of ways to implement queues, but my favorite is called a *ring +buffer*. It preserves everything that's great about arrays while letting us +incrementally remove items from the front of the queue. + +这里有很多种方式实现队列,但我最喜欢的是*环状缓存*。他保留了数组的很多好东西,同时影响我们增量的从队列的前方一处事物。 + +Now, I know what you're thinking. If we remove items from the beginning of the +array, don't we have to shift all of the remaining items over? Isn't that slow? + +现在,我知道你在想什么。如果我们从数组的前方移除东西,我们不是需要将所有剩下的都移动一次吗》这不是很慢吗? + +This is why they made us learn linked lists -- you can remove nodes from them +without having to shift things around. Well, it turns out you can implement a +queue without any shifting in an array too. I'll walk you through it, but first +let's get precise on some terms: + +这既是为什么他们让我们学习链接列表——你可以从他们中移除一个节点,而无需移动东西。好吧,其实你可以用数组实现一个队列而无需移动东西。我会展示给你看,但是首先让我们预习一些术语: + + * The **head** of the queue is where requests are *read* from. The head is the + oldest pending request. + + * 队列的**头**是请求*读取*的地方。他的头是最早发出的请求。 + + * The **tail** is the other end. It's the slot in the array where the next + enqueued request will be *written*. Note that it's just *past* the end of + the queue. You can think of it as a half-open range, if that helps. + + * **尾**是另一种终点。他是数组中下一个入队请求要*写*的地方。足以他只是将队列的终点的下一个。你可以将其理解为一个半开半闭区间,如果这有帮助的话。 + +Since `playSound()` appends new requests at the end of the array, the head +starts at element zero and the tail grows to the right. + +由于 `playSound()` 未数组的结尾添加了一个新的请求,他的头上在元素0上而他的尾向右增长。 + +An array of events. The head points to the first element, and the tail grows to the right. + +Let's code that up. First, we'll tweak our fields a bit to make these two +markers explicit in the class: + +让我们把他们编码处理。首先,我们让这两个标记在类中的意义清晰: + +^code head-tail + +In the implementation of `playSound()`, `numPending_` has been replaced with +`tail_`, but otherwise it's the same: + +在 `playSound()` 的实现中,`numPending_`被`tail_`取代,但是其他都是一样的: + +^code tail-play + +The more interesting change is in `update()`: + +更有趣的变化实在`update()`中: + +^code tail-update + +We process the request at the head and then discard it by advancing the head +pointer to the right. We detect an empty queue by +seeing if there's any distance between the head and tail. + +我们再头部处理然后通过将头部指针向右移动来消除它。我们定义一个空队列为在头尾之间没有任何距离。 + + + +Now we've got a queue -- we can add to the end and remove from the front. +There's an obvious problem, though. As we run requests through the queue, the +head and tail keep crawling to the right. Eventually, `tail_` hits the end +of the array, and party time is over. This is where it +gets clever. + +现在,我们获得了一个队列——我们可以向尾部添加从前面移除。这里有一个很明显的问题。就在我们想队列添加请求的时候,头和尾继续向右移动。最终`tail_`碰到了数组的尾部,欢乐时间结束了。这个方法的灵巧之处就要出现了。 + + + +The same array as before but now the head is moving towards the right, leaving available cells on the left. + +Notice that while the tail is creeping forward, the *head* is too. That means +we've got array elements at the *beginning* of the array that aren't being used +anymore. So what we do is wrap the tail back around to the beginning of the +array when it runs off the end. That's why it's called a *ring* buffer -- it acts +like a circular array of cells. + +注意当尾部向后移动时,*头部*也是如此。这就意味着我们再数组*开始*部分的元素再也没有使用了。所以欧美做的就是将尾部返回到数组的头部,当他抵达结束的时候。这就是为什么他被称为*环状*缓存,他表现的相识一个环状的数组。 + +The array wraps around and now the head can circle back to the beginning. + +Implementing that is remarkably easy. When we enqueue an item, we just need to +make sure the tail wraps around to the beginning of the array when it reaches +the end: + +实现这个非常简单。当我们入队一个事物时,我们只需要保证尾部在抵达结束的时候回折到数组的开头。 + +^code ring-play + +Replacing `tail_++` with an increment modulo the array size wraps the tail back +around. The other change is the assertion. We need to ensure the queue doesn't +overflow. As long as there are fewer than `MAX_PENDING` requests in the queue, +there will be a little gap of unused cells between the head and the tail. If the +queue fills up, those will be gone and, like some weird backwards Ouroboros, the +tail will collide with the head and start overwriting it. The assertion ensures +that this doesn't happen. + +替代`tail++`为一个以数组长度的模的增量将尾部回折回来。另一个改变是断言。我们得保证队列不会溢出。只要这里有少于`MAX_PENDING`的请求子啊队列中,这里就贵在头和尾之间有一些没有使用的间隔。如果队列满了,这些都不会有了,就像奇怪的衔尾蛇一样,尾部会遇到头部然后复写它。这个断言保证了这点不会发生。 + +In `update()`, we wrap the head around too: + +在`update()`阿忠,我们也回折了头: + +^code ring-update + +There you go -- a queue with no dynamic allocation, +no copying elements around, and the cache-friendliness of a simple array. + +这样就好——一个没有动态分配,没有数据拷贝,缓存友好的简单数组实现的队列完成了。 + + + +### Aggregating requests + +### 合并请求 + +Now that we've got a queue in place, we can move onto the other problems. The +first is that multiple requests to play the same sound end up too loud. Since we +know which requests are waiting to be processed now, all we need to do is merge +a request if it matches an already pending one: + +现在我们有一个队列了,我们可以解决其他问题了。首先是多重请求播放统一音频最终会让其播放的太响了。由于我们知道哪些请求在等待处理,我们需要做的所有事情就是将一个请求和早先处理的同一请求合并。 + +^code drop-dupe-play + +When we get two requests to play the same sound, we collapse them to a single +request for whichever is loudest. This "aggregation" is pretty rudimentary, but +we could use the same idea to do more interesting batching. + +但我们有两个请求播放同一音频时,我们将他们合并成一个播放最响的请求。这一个“合并”非常简陋,但是我们可以用同样的方法做很多有趣的合并。 + +Note that we're merging when the request is *enqueued*, not when it's +*processed*. That's easier on our queue since we don't waste slots on redundant +requests that will end up being collapsed later. It's also simpler to implement. + +注意想先我们在请求*入队*时合并,而不是*运行*时。在队列中处理更加容易,因为我们不需要在最后会被合并的多余请求上浪费时间。这也更加容易被实现。 + +It does, however, put the processing burden on the caller. A call to +`playSound()` will walk the entire queue before it returns, which could be slow if the queue is large. It may make more sense to +aggregate in `update()` instead. + +但是,这确实将处理的职责放在了调用者肩上。一个对`playSound()`的调用会遍历它返回的整个队列,如果队列很长,那么他会很慢。如果再`update()`中实现也许更加合理。 + + + +There's something important to keep in mind here. The window of "simultaneous" +requests that we can aggregate is only as big as the queue. If we process +requests more quickly and the queue size stays small, then we'll have fewer +opportunities to batch things together. Likewise, if processing lags behind and +the queue gets full, we'll find more things to collapse. + +这里有些要点要记住。我们能够合并的“同步”请求窗口只有队列那么大。如果我们足够快的处理请求,队列的体积就会保持较小,然后我们就有更少的机会将东西组合在一起。就像这样,如果处理慢了,队列满了,我们能找到更多的东西合并。 + +This pattern insulates the requester from knowing when the request gets +processed, but when you treat the entire queue as a live data structure to be +played with, then lag between making a request and processing it can visibly +affect behavior. Make sure you're OK with that before doing this. + +这个模式隔离了请求者知道何时请求被处理,但是当你将整个队列视为一个活动的数组结构交流,那么发出请求和处理它之间的延迟可以显式的影响行为。在你做这个东西之前保证你对此没有问题。 + +### Spanning threads + +### 分离线程 + +Finally, the most pernicious problem. With our synchronous audio API, whatever +thread called `playSound()` was the thread that processed the request. That's +often not what we want. + +最终,最险恶的问题。通过同步音频API,调用`playSound()`的线程就是处理请求的线程。这通常不是我们想要的。 + +On today's multi-core hardware, you need more than +one thread if you want to get the most out of your chip. There are infinite ways +to distribute code across threads, but a common strategy is to move each domain +of the game onto its own thread -- audio, rendering, AI, etc. + +在今日多核硬件上,你需要多余一个线程来使用你芯片的大部分。这里有无数的范式在线程间分散代码,但是最通用的策略是将每一个独立的域分散道一个线程——音频,渲染,AI等等。 + + + +We're in good shape to do that now that we have three critical pieces: + +我们很容易就能做到这一带你是因为我们有三个主要部分: + + 1. The code for requesting a sound is decoupled from the code that plays it. + + 1. 请求音频的代码与播放音频的代码解耦。 + + 2. We have a queue for marshalling between the two. + + 2. 我们有一个队列在两者之间整理他们。 + + 3. The queue is encapsulated from the rest of the program. + + 3. 队列与其他程序部分是隔离的。 + +All that's left is to make the methods that modify the queue -- `playSound()` +and `update()` -- thread-safe. Normally, I'd whip up some concrete code to do +that, but since this is a book about architecture, I don't want to get mired in +the details of any specific API or locking mechanism. + +剩下要做的事情就是写修改队列的方法——`playSound()`和`update()`——线程安全。通常,我会写一写具体代码完成之,但是由于这里是一本关于架构的书,我不想让其着眼于一些特定的API或者锁机制。 + +At a high level, all we need to do is ensure that the queue isn't modified +concurrently. Since `playSound()` does a very small amount of work -- basically +just assigning a few fields -- it can lock without blocking processing for long. +In `update()`, we wait on something like a condition variable so that we don't +burn CPU cycles until there's a request to process. + +在更高层次,所有我们需要做的技术保证队列不是同时被修改的。由于`playSound()`只做了一点点事情——基本上就是声明一些字段。——不必阻塞线程太长时间就可以锁住。在`update()`中,我们等待一些条件变量,这样直到有请求需要处理时才会消耗CPU循环。 + +## Design Decisions + +## 设计决策 + +Many games use event queues as a key part of their communication structure, and +you can spend a ton of time designing all sorts of complex routing and filtering +for messages. But before you go off and build something like the Los Angeles +telephone switchboard, I encourage you to start simple. Here's a few starter +questions to consider: + +很多游戏使用时间队列作为他们交流结构的关键部分,你可以活很多时间设计各种复杂的路径和消息过滤器。但是在你出发构建一些洛杉矶电话交换机之类的东西之前,我们推荐你做一点简单的东西。这里是几个需要在开始时思考的问题: + +### What goes in the queue? + +### 队列中存储了什么? + +I've used "event" and "message" interchangeably so far because it mostly doesn't +matter. You get the same decoupling and aggregation abilities regardless of what +you're stuffing in the queue, but there are some conceptual differences. + +到目前为止,我交替使用了“事件”和“消息”因为它大多时候不重要。无论你在队列中塞了什么都可以获得解耦和合并能力,但是还是有几个地方不同。 + + * **If you queue events:** + + * **如果你存储事件:** + + An "event" or "notification" describes something that *already* happened, + like "monster died". You queue it so that other objects can *respond* to the + event, sort of like an asynchronous Observer pattern. + + 一个“事件”或者“通知”描绘*已经*发生的事情,比如“怪物死了”。你入队它这样其他的对象可以对这个事件作出*回应*,有点像异步的观察者模式。 + + * *You are likely to allow multiple listeners.* Since the queue contains + things that already happened, the sender probably doesn't care who + receives it. From its perspective, the event is in the past and is + already forgotten. + + * *你很可能运行多个监听者。*由于队列包含的是已经发生的事情,发送者可能不关心谁接受它。从这个层面来说,事件发生在过去,早已被遗忘。 + + * *The scope of the queue tends to be broader.* Event queues are often + used to *broadcast* events to any and all interested parties. To allow + maximum flexibility for which parties can be interested, these queues + tend to be more globally visible. + + * *队列的余地一般比较广阔。*事件队列通常被使用为*广播*事件到任何感兴趣的部分。为了允许最大程度满足哪些部分感兴趣的灵活性,这些队列一般是全局可见的。 + + * **If you queue messages:** + + * **如果你存储消息:** + + A "message" or "request" + describes an action that we *want* to happen *in the future*, like "play + sound". You can think of this as an asynchronous API to a service. + + “消息”或“请求”描绘了我们*想要*发生*在未来*,比如“播放声音”。你可以将其视为服务的异步API。 + + + + * *You are more likely to have a single + listener.* In the example, the queued messages are requests specifically + for *the audio API* to play a sound. If other random parts of the game + engine started stealing messages off the queue, it wouldn't do much + good. + + * *你更可能只有一个监听者。*在这个例子中,存储的消息特定请求*音频API*播放声音。如果有些引擎的随机其他部分从队列总可以拿走消息,这就做不了什么好事了。 + + + +### Who can read from the queue? + +### 谁能从队列中读取? + +In our example, the queue is encapsulated and only the `Audio` class can read +from it. In a user interface's event system, you can register listeners to your +heart's content. You sometimes hear the terms "single-cast" and "broadcast" to +distinguish these, and both styles are useful. + +在我们的例子中,队列是密封的,只有`Audio`类可以从中读取。在一个用户交互的事件系统,你可以在你的核心内容中注册监听器。你有事可以听到术语“单播”和“广播”来描述它,两者都很有用。 + + * **A single-cast queue:** + + * **一个单播队列:** + + This is the natural fit when a queue is part of a class's API. Like in our + audio example, from the caller's perspective, they just see a + `playSound()` method they can call. + + 这在队列是类API的一部分时时很自然的。就像我们音频的例子,从调用者的角度,他们只能看到一个他们可以调用的`playSound()`方法。 + + * *The queue becomes an implementation detail of the reader.* All the + sender knows is that it sent a message. + + * *对垒变成了读取这的实现细节。*发送者知道的所有事情就是发送一条消息。 + + * *The queue is more encapsulated.* All other things being equal, more + encapsulation is usually better. + + * *队列更加封装。*其他都一样的时候,更多的封装更方便。 + + * *You don't have to worry about contention between listeners.* With + multiple listeners, you have to decide if they *all* get every item + (broadcast) or if *each* item in the queue is parceled out to *one* + listener (something more like a work queue). + + * *你无须担心监听者之间的竞争。*使用多个监听者,你需要决定是否他们*全部*都要获得每一个事物(广播)还是*每一个*队列中的事物都被分给*同一个*监听者(更加像工作队列)。 + + In either case, the listeners may end up doing redundant work or + interfering with each other, and you have to think carefully about the + behavior you want. With a single listener, that complexity disappears. + + 在两种情况下,监听者最终都做了多余的事情或者域其他相互打扰,你得谨慎考虑你想要的行为。通过一个单一的监听者,复杂性消失了。 + + * **A broadcast queue:** + + * **一个广播队列:** + + This is how most "event" systems work. If you have ten listeners when an + event comes in, all ten of them see the event. + + 这是很多“事件”系统工作的方法。如果你有十个监听者,一个事件进来,所有的都能看到这个事件。 + + * *Events can get dropped on the floor.* A corollary to the previous point + is that if you have *zero* listeners, all zero of them see the event. In + most broadcast systems, if there are no listeners at the point in time + that an event is processed, the event gets discarded. + + * *事件可能会掉到地板上。*前面那点的必然推论就是如果你有*零个*监听者,没有谁能看到这个事件。在大多数广播系统中,如果在时间处理的时候没有监听者,事件就消失了。 + + * *You may need to filter events.* Broadcast queues are often widely + visible to much of the program, and you can end up with a bunch of + listeners. Multiply lots of events times lots of listeners, and you end + up with a ton of event handlers to invoke. + + * *你也许需要过滤事件。*广播队列经常对程序的所有部分广泛可见,最终你会获得一系列监听者。将很多事件乘以很多监听者,你会获取一大堆事件处理器。 + + To cut that down to size, most broadcast event systems let a listener + winnow down the set of events they receive. For example, they may say + they only want to receive mouse events or events within a certain + region of the UI. + + 为了消减大小,大多数广播事件系统让一个监听者筛出他们需要接受的事件。比如,他们可能会说他们只想要接受鼠标事件或者在某一UI区域内的事件。 + + * **A work queue:** + + * **一个工作队列:** + + Like a broadcast queue, here you have multiple listeners too. The difference + is that each item in the queue only goes to *one* of them. This is a common + pattern for parceling out jobs to a pool of concurrently running threads. + + 就像一个广播队列,这里你有多重监听器。不同之处在于每一个队列的东西只会去他们*其中之一*。在将工作打包给同时运行的线程池中是一个常见的应用。 + + * *You have to schedule.* Since an item only goes to one listener, the + queue needs logic to figure out the best one to choose. This may be as + simple as round robin or random choice, or it could be some more complex + prioritizing system. + + * *你得规划。*由于一个事物只给一个监听器,队列需啊哟逻辑指出最好的选项。这也许像round robin算法或者乱序选择一样简单,或者可以使用更加复杂的优先度系统。 + +### Who can write to the queue? + +### 谁能写入队列? + +This is the flip side of the previous design choice. This pattern works with all +of the possible read/write configurations: +one-to-one, one-to-many, many-to-one, or many-to-many. + +这是前一个设计决策的另一面。这个模式工作在所有可能的读/写设置上一对一,一对多,多对一,多对多。 + + + + * **With one writer:** + + * ** 使用一个写入器:** + + This style is most similar to the synchronous Observer pattern. You have one + privileged object that generates events that others can then receive. + + 这种风格和同步的观察者模式很像。你有一个特定对象收集所有可以接受的事件。 + + * *You implicitly know where the event is coming from.* Since there's only + one object that can add to the queue, any listener can safely assume + that's the sender. + + * *你特定的知道事件是从哪里来的。*由于这里只有一个对象可以向队列添加,任何监听器都可以安全的假设那就是发送者。 + + * *You usually allow multiple readers.* You can have a + one-sender-one-receiver queue, but that starts to feel less like the + communication system this pattern is about and more like a vanilla queue data + structure. + + * *你通常允许多个读者。*你可以有一个单发送者对单接受者的队列,但是感觉这这个模式下的沟通系统更像一个添了香料的队列数据结构。 + + * **With multiple writers:** + + * **使用多个写入器:** + + This is how our audio engine example works. Since `playSound()` is a public + method, any part of the codebase can add a request to the queue. "Global" or + "central" event buses work like this too. + + 这是我们例子中音频引擎工作的方式。由于`playSound()`是一个公开的方法,代码库的任何部分都能给队列添加一个请求。“全局”或“中心”事件总线像这样工作。 + + * *You have to be more careful of cycles.* Since anything can + potentially put something onto the queue, it's easier to accidentally + enqueue something in the middle of handling an event. If you aren't + careful, that may trigger a feedback loop. + + * *你得更小心环路。*由于任何东西都有可能向队列中添加东西,这更容易意外的在处理一个事件的时候加入一个事件。如果你不小心,那可能会出发一个反馈循环。 + + * *You'll likely want some reference to the sender in the event itself.* + When a listener gets an event, it doesn't know who sent it, since it + could be anyone. If that's something they need to know, you'll want to + pack that into the event object so that the listener can use it. + + * *你需要在事件中添加一些对发送者的引用。*当监听者街道事件,他不是谁发送的,因为那可能是任何人。如果有什么他们需要知道的事情,你得将其打包到事件对象中去,这样监听者才可能使用它。 + +### What is the lifetime of the objects in the queue? + +### 对象在队列中的生命周期如何? + +With a synchronous notification, execution doesn't return to the sender until +all of the receivers have finished processing the message. That means the +message itself can safely live in a local variable on the stack. With a queue, +the message outlives the call that enqueues it. + +使用同步的通知,知道所有的接受者完成了处理消息才会返回发送者。这意味着消息本身可以安全的存在栈的一个局部变量中。使用一个队列,消息比让它入队的调用活得长。 + +If you're using a garbage collected language, you don't need to worry about this +too much. Stuff the message in the queue, and it will stick around in memory as +long as it's needed. In C or C++, it's up to you to ensure the object lives long +enough. + +如果你是用一个有垃圾回收的语言,你无需过度担心这个。将消息存到队列中,他会一直存到需要他的时候。在C或C++中,这由你来保证对象活的足够长。 + + * **Pass ownership:** + + * **传递所有权:** + + This is the traditional way to do things when + managing memory manually. When a message gets queued, the queue claims it + and the sender no longer owns it. When it gets processed, the receiver takes + ownership and is responsible for deallocating it. + + 这是手动管理内存的传统方法。当一个消息入队时,队列拥有了它,发送者不在拥有它。当他被处理是,接受者获取了所有权,负责销毁他。 + + + + * **Share ownership:** + + * **共享所有权:** + + These days, now that even C++ programmers are more comfortable with garbage + collection, shared ownership is more acceptable. + With this, the message sticks around as long as anything has a reference to + it and is automatically freed when forgotten. + + 现在,甚至C++程序员都更适宜垃圾回收了,分享所有权更加可接受。通过这样,消息只要有东西对其有引用就会存在,当被遗忘的时候自动释放。 + + + + * **The queue owns it:** + + * **队列拥有它:** + + Another option is to have messages *always* live on + the queue. Instead of allocating the message itself, the sender requests a + "fresh" one from the queue. The queue returns a reference to a message + already in memory inside the queue, and the sender fills it in. When the + message gets processed, the receiver refers to the same message in the + queue. + + 另一个选项是让消息*永远*在队列中存在。发送者不是自己分配消息,它向内存请求一个“新的”。队列返回一个队列中已经在内存的消息的引用,接受者引用队列中相同的消息。 + + + +## See Also + +## 参见 + + * I've mentioned this a few times already, but in many ways, this pattern is + the asynchronous cousin to the well-known Observer pattern. + + * 我在之前提到了几次,但是很大程度上,这个模式是广为人知的观察者模式的异步。 + + * Like many patterns, event queues go by a number of aliases. One established + term is "message queue". It's usually referring to a higher-level + manifestation. Where our event queues are *within* an application, message + queues are usually used for communicating *between* them. + + 就像其他很多模式一样,事件队列有很多别名。一个确定的是“消息队列”。这通常指代一个更高层次的实现。事件队列在一个应用*中*,消息队列通常被用为在应用*间*交流。 + + Another term is "publish/subscribe", sometimes abbreviated to "pubsub". Like + "message queue", it usually refers to larger distributed systems unlike + the humble coding pattern we're focused on. + + 另一个术语是“发布/提交”,优势被缩写为“pubsub”。就像“消息队列”一样,这通常指代更待的分布式系统,而不是我们现在关注的这个模式。 + + * A [finite state machine](http://en.wikipedia.org/wiki/Finite-state_machine), + similar to the Gang of Four's State pattern, requires a stream of inputs. If you + want it to respond to those asynchronously, it makes sense to queue them. + + * 一个确定状态机,很像GoF的状态模式,需要一个输入流。如果你想要异步回应他们,用队列存储是一个有道理的选项。 + + When you have a bunch of state machines sending messages to each other, each + with a little queue of pending inputs (called a *mailbox*), then you've + re-invented the [actor model](http://en.wikipedia.org/wiki/Actor_model) of + computation. + + 当你有一对状态机相互发送消息时,每一个都有一个小小的未处理队列(被称为一个*信箱*),然后你需要从新计算的actor model。 + + * The [Go](http://golang.org/) programming language's built-in "channel" type + is essentially an event or message queue. + + * Go于洋内建了“信道”类型本质上是一个事件或消息队列。 + diff --git a/book/flyweight.markdown b/book/flyweight.markdown new file mode 100644 index 0000000..bd61316 --- /dev/null +++ b/book/flyweight.markdown @@ -0,0 +1,451 @@ +^title Flyweight +^section Design Patterns Revisited + +# 享元模式 + +The fog lifts, revealing a majestic old growth forest. Ancient hemlocks, +countless in number, tower over you forming a cathedral of greenery. The stained +glass canopy of leaves fragments the sunlight into golden shafts of mist. +Between giant trunks, you can make out the massive forest receding into the +distance. + +迷雾散尽,露出了古朴庄严的森林。无数古老的铁杉,在你头顶编制成绿色的穹顶。阳光在树叶间破碎成金色的顶棚。在巨大的树干间眺望,远处的森林渐渐隐去。 + +This is the kind of otherworldly setting we dream of as game developers, and +scenes like these are often enabled by a pattern whose name couldn't possibly be +more modest: the humble Flyweight. + +这是我们游戏开发者梦想的超凡设置,这样的场景通常由一个模式支撑着,他的名字谦虚至极:享元模式。 + +## Forest for the Trees + +I can describe a sprawling woodland with just a few sentences, but actually +*implementing* it in a realtime game is another story. When you've got an entire +forest of individual trees filling the screen, all that a graphics programmer +sees is the millions of polygons they'll have to somehow shovel onto the GPU +every sixtieth of a second. + +我用几句话就能描述一片巨大的森林,但是在实时游戏中做这件事就完全是另外一件事了。当你的屏幕上需要一整个森林的时候,程序员看到的是每秒需要送到GPU六十次的百万多边形。 + +We're talking thousands of trees, each with detailed geometry containing +thousands of polygons. Even if you have enough *memory* to describe that forest, +in order to render it, that data has to make its way over the bus from the CPU +to the GPU. + +我们讨论的是成千上万的树,每一棵都有上千的多边形组成。就算你有足够的内存空间描述森林,渲染的过程中,CPU到GPU的部分也太过繁忙了。 + +Each tree has a bunch of bits associated with it: + +每棵树都有一系列与之相关的位: + +* A mesh of polygons that define the shape of the trunk, branches, and greenery. +* Textures for the bark and leaves. +* Its location and orientation in the forest. +* Tuning parameters like size and tint so that each tree looks different. + +* 定义树干,树枝和树叶的形状的多边形网格。 +* 树皮和树叶的纹理。 +* 在森林中它的位置和方向。 +* 栗色大小和色彩的参数,使每一棵树都看起来与众不同。 + +If you were to sketch it out in code, you'd have something like this: + +如果你用代码表示,那么你会有这样的一些东西: + +^code heavy-tree + +That's a lot of data, and the mesh and textures are particularly large. An entire +forest of these objects is too much to throw at the GPU in one frame. +Fortunately, there's a time-honored trick to handling this. + +这是一大堆数据,多边形网格和纹理体积非常大。整个这样的森林在一帧的时间就交给GPU是太过了。幸运的是,有一种时间技巧来处理它。 + +The key observation is that even though there may be thousands of trees in the +forest, they mostly look similar. They will likely all use the same mesh and textures. That means most of the fields in +these objects are the *same* between all of those instances. + +关键点在于,哪怕森林里有千千万万的树,他们大多数看起来长得一模一样。他们也许使用了*相同的*网格和纹理。这意味着大多数树对象内部存储的数据是一样的。 + + + + + +A row of trees, each of which has its own Mesh, Bark, Leaves, Params, and Position. + + + +We can model that explicitly by splitting the object in half. First, we pull +out the data that all trees have in common and move it +into a separate class: + +我们可以通过将对象切为两部分来更加明确的定义它。第一,我们将树共有的数据拿出来放到另一个类中: + +^code tree-model + +The game only needs a single one of these, since there's no reason to have the +same meshes and textures in memory a thousand times. Then, each *instance* of a +tree in the world has a *reference* to that shared `TreeModel`. What remains in +`Tree` is the state that is instance-specific: + +游戏只需要一个这种类,因为没有必要在内存中把相同的网格和纹理重复一千遍。然后每个游戏世界中类的实例有一个对这个共享TreeModel的引用。留在树对象中的是那些与单个树相关的数据: + +^code split-tree + +You can visualize it like this: + +你可以将其想象成这样: + +A row of trees each with its own Params and Position, but pointing to a shared Model with a Mesh, Bark, and Leaves. + + + +This is all well and good for storing stuff in main memory, but that doesn't +help rendering. Before the forest gets on screen, it has to work its way over to +the GPU. We need to express this resource sharing in a way that the graphics +card understands. + +把所有的东西都存在主存里是很好的,但是这不利于渲染。在森林到屏幕上之前,它得先到GPU。我们需要用显卡可以理解的方式共享 + +## A Thousand Instances + +## 一千个实例 + +To minimize the amount of data we have to push to the GPU, we want to be able to +send the shared data -- the `TreeModel` -- just *once*. Then, separately, we +push over every tree instance's unique data -- its position, color, and scale. +Finally, we tell the GPU, "Use that one model to render each of these +instances." + +为了减少我们需要推送到GPU的数据量,我们想要只把共享的数据——TreeModel——只发送一次。然后,我们分别把每个树独特的数据——位置,颜色,大小。最后,我们告诉GPU,“使用这一个模型渲染每一个实例”。 + +Fortunately, today's graphics APIs and cards +support exactly that. The details are fiddly and out of the scope of this book, +but both Direct3D and OpenGL can do something called [*instanced +rendering*](http://en.wikipedia.org/wiki/Geometry_instancing). + +幸运的是,今日的图形接口和显卡正好支持这一点。这些细节繁琐且超出了这部书的范围,但是Direct3D和OpenGL都可以做实例渲染。 + +In both APIs, you provide two streams of data. The first is the blob of common +data that will be rendered multiple times -- the mesh and textures in our +arboreal example. The second is the list of instances and their parameters that +will be used to vary that first chunk of data each time it's drawn. With a +single draw call, an entire forest grows. + +在这些接口中,你提供两个两个数据流。第一部分是一块需要渲染多次的共同数据——在我们的例子中是树的网格和纹理。第二部分是实例的列表以及每次绘制第一部分时需要使用的参数。只要调用一次绘图,整个森林都会生长出来。 + + + +## The Flyweight Pattern + +## 享元模式 + +Now that we've got one concrete example under our belts, I can walk you through +the general pattern. Flyweight, like its name implies, comes into play when you +have objects that need to be more lightweight, generally because you have too +many of them. + +好了,我们已经看了一个具体的例子,下面我介绍模式的通用部分。享元,就像它名字暗示的那样,当你需要更加轻量级的类事使用,大多数情况是因为你有太多这种类了。 + +With instanced rendering, it's not so much that they take up too much memory as +it is they take too much *time* to push each separate tree over the bus to the +GPU, but the basic idea is the same. + +渲染的实例中,消耗的内存没有将每棵树通过总线送到GPU消耗的*时间*多,但是基本要点是一样的。 + +The pattern solves that by separating out an object's data into two kinds. The +first kind of data is the stuff that's not specific to a single *instance* of +that object and can be shared across all of them. The Gang of Four calls this +the *intrinsic* state, but I like to think of it as the "context-free" stuff. In +the example here, this is the geometry and textures for the tree. + +这个模式通过将对对象的数据分为两种来解决这个问题。第一种数据是没有特定字幕是哪一个对象的*实例*,因此可以在对象间分享。Gof称之为*固有*状态,但是我更喜欢将其视为*上下文无关*部分。在这里的例子中,这是苏的网格和纹理。 + +The rest of the data is the *extrinsic* state, the stuff that is unique to that +instance. In this case, that is each tree's position, scale, and color. Just +like in the chunk of sample code up there, this pattern saves memory by sharing +one copy of the intrinsic state across every place where an object appears. + +数据的剩余部分是*外在*状态,那些对实例独一无二的东西。在这个例子中,是每棵树的位置,拉伸和颜色。就像这里显示的示例代码块一样,这种模式通过在每个对象出现的时候共享一份固有状态,来节约内存。 + +From what we've seen so far, this seems like basic resource sharing, +hardly worth being called a pattern. That's partially because in this example +here, we could come up with a clear separate *identity* for the shared state: +the `TreeModel`. + +就我们目前而言就,这看上去像是一个基础的资源共享,很难被称为一种模式。这部分是因为在这个例子中,我们可以为共享状态划出一个清晰的*身份*:`TreeModel`。 + +I find this pattern to be less obvious (and thus more clever) when used in cases +where there isn't a really well-defined identity for the shared object. In those +cases, it feels more like an object is magically in multiple places at the same +time. Let me show you another example. + +我发现当共享对象没有一个好好设计的实体时,使用这种模式就不那么明显(使用者也就越发精明)。在这些情况下,这看上去是一个对象在同时被魔术般的分配到了多个地方。让我展示给你另外一个例子。 + +## A Place To Put Down Roots + +## 放根的地方 + +The ground these trees are growing on needs to be represented in our game too. +There can be patches of grass, dirt, hills, lakes, rivers, and whatever other +terrain you can dream up. We'll make the ground *tile-based*: the surface of the +world is a huge grid of tiny tiles. Each tile is covered in one kind of terrain. + +这些树长出来的地方也需要在我们的游戏中表示。这里可能有草,泥土,丘陵,湖泊,河流,以及其它任何你可以想到的地形。我们*基于区块*建立地表:世界的表面被划分为由微小区块组成的巨大网格。每一个区块都是由一种地形覆盖。 + +Each terrain type has a number of properties that affect gameplay: + +每一种地形类型都有一系列特性会影响游戏玩法: + +* A movement cost that determines how quickly players can move through it. +* A flag for whether it's a watery terrain that can be crossed by boats. +* A texture used to render it. + +* 一个移动代价决定了玩家能够多快的穿过它。 +* 一个标识表面了这是不是一个我们能够通过船穿过的有水的地形。 +* 一个纹理用来渲染它。 + +Because we game programmers are paranoid about efficiency, there's no way we'd +store all of that state in each tile in the world. +Instead, a common approach is to use an enum for terrain types: + +因为我们游戏程序员偏执于效率,我们不会在每一个区块中保存这些状态。相反,一个通用的方式是为每一种地形使用一个枚举类型。 + + + +^code terrain-enum + +Then the world maintains a huge grid of those: + +然后,这个世界这样管理巨大的网格: + + + +^code enum-world + + + +To actually get the useful data about a tile, we do something like: + +为了实际获得区块的有用数据,我们做了一些这样的事情: + +^code enum-data + +You get the idea. This works, but I find it ugly. I think of movement cost and +wetness as *data* about a terrain, but here that's embedded in code. Worse, the +data for a single terrain type is smeared across a bunch of methods. It would be +really nice to keep all of that encapsulated together. After all, that's what +objects are designed for. + +你知道我的意思了。这可行,但是我觉得很丑。运行代价和湿度是地形的*数据*,但这嵌入了代码。更糟的是,一个简单地形的数据在横跨在一堆代码中。如果能够将这些包裹起来就好了。毕竟,那是为什么我们设计了对象。 + +It would be great if we could have an actual terrain *class*, like: + +如果我们有实际的地形*类*就好了,像这样: + + + +^code terrain-class + + + +But we don't want to pay the cost of having an instance of that for each tile in +the world. If you look at that class, you'll notice that there's actually +*nothing* in there that's specific to *where* that tile is. In flyweight terms, +*all* of a terrain's state is "intrinsic" or "context-free". + +但是我们不想为每一个区块都保存一个实例。如果你看看这个类里面,你会发现里面实际上*什么也没有*,唯一特别的是区块在*哪里*。用享元的术语讲,区块的所有状态都是“内在的”或者说“上下文无关的”。 + +Given that, there's no reason to have more than one of each terrain type. Every +grass tile on the ground is identical to every other one. Instead of having the +world be a grid of enums or Terrain objects, it will be a grid of *pointers* to +`Terrain` objects: + +鉴于此,我们没有必要保持多余一个地形类型。每一种地面上的草区块和其他的没有什么不同。游戏世界不是由地形区块对象网格组成的,而是由`Terrain`对象*指针*网格组成的: + +^code world-terrain-pointers + +Each tile that uses the same terrain will point to the same terrain instance. + +每一个使用相同地形的区块会指向相同的地形实例。 + +A row of tiles. Each tile points to either a shared Grass, River, or Hill object. + +Since the terrain instances are used in multiple places, their lifetimes would +be a little more complex to manage if you were to dynamically allocate them. +Instead, we'll just store them directly in the world: + +由于地形实例在很多地方使用,如果你想要动态组织他们,他们的生命周期会有点复杂。取而代之的是,我们直接字啊游戏世界中存储他们。 + +^code world-terrain + +Then we can use those to paint the ground like this: + +然后我们可以像这样来描绘地面: + + + +^code generate + + + +Now instead of methods on `World` for accessing the terrain properties, we can +expose the `Terrain` object directly: + +现在不需要`World`中的方法来接触到地形属性,我们可以直接暴露出`Terrain`对象。 + +^code get-tile + +This way, `World` is no longer coupled to all sorts of details of terrains. If +you want some property of the tile, you can get it right from that object: + +用这种方式,`World`不再是存储各种地形的细节。如果你想要某一区块的属性,你可以直接从你那个对象获得: + +^code use-get-tile + +We're back to the pleasant API of working with real objects, and we did this +with almost no overhead -- a pointer is often no larger than an enum. + +我们回到了与真实对象工作的愉快API,我们也几乎没有开销——一个指针通常不比一个枚举大。 + +## What About Performance? + +## 性能如何? + +I say "almost" here because the performance bean counters will rightfully want +to know how this compares to using an enum. Referencing the terrain by pointer +implies an indirect lookup. To get to some terrain data like the movement cost, +you first have to follow the pointer in the grid to find the terrain object and +then find the movement cost there. Chasing a pointer like this can cause a cache miss, which can slow things down. + +我在这里说几乎是因为性能偏执狂肯定会想要知道它和使用枚举比起来如何。通过指针解引用获取地形需要一次间接跳转。为了获得移动代价这样的地形数据,你首先需要跟着网格中的指针找到地形对象,然后再找到移动代价。跟踪这样的指针会导致缓存不命中,降低运行速度。 + + + +As always, the golden rule of optimization is *profile first*. Modern computer +hardware is too complex for performance to be a game of pure reason anymore. In +my tests for this chapter, there was no penalty for using a flyweight over an +enum. Flyweights were actually noticeably faster. But that's entirely dependent +on how other stuff is laid out in memory. + +就像往常一样,优化的金科玉律是*概述有限*。现代计算机硬件过于复杂,性能只是游戏的一个原因。在我这章做的测试中,享元较枚举没有什么性能的优势。享元实际上明显更快。但是这也完全取决于内存中的事物是如何排列的。 + +What I *am* confident of is that using flyweight objects shouldn't be dismissed +out of hand. They give you the advantages of an object-oriented style without +the expense of tons of objects. If you find yourself creating an enum and doing +lots of switches on it, consider this pattern instead. If you're worried about +performance, at least profile first before changing your code to a less +maintainable style. + +我*可以*自信使用享元对象不会被搞到不可收拾。他给了你面向对象的优势,而没有牺牲一堆对象。如果你发现自己创建了一个枚举,然后在他上面做了很多分支跳转,考虑一下这个模式吧。如果你担心性能,在你把代码编程更加不可控的风格之前至少测试一下。 + +## See Also + +## 参见 + + * In the tile example, we just eagerly created an instance for each terrain + type and stored it in `World`. That made it easy to find and reuse the + shared instances. In many cases, though, you won't want to create *all* of + the flyweights up front. + + 在区块的例子中,我们只是为每一种地形创建一个实例然后存储在`World`中。这也许更好找到和重用这些实例。但是在很多情况下,你不会在一开始就创建*所有*享元。 + + If you can't predict which ones you actually need, it's better to create + them on demand. To get the advantage of sharing, when you request one, you + first see if you've already created an identical one. If so, you just return + that instance. + + 如果你不能预料到哪一个是你实际上需要的,最好在需要时采取创建。为了保持共享的优势,当你请求一个的时候,你首先看看你是否已经创建了一个相同的实例。如果确实如此,那么你只需返回那个实例。 + + This usually means that you have to encapsulate construction behind some + interface that can first look for an existing object. Hiding a constructor + like this is an example of the Factory Method pattern. + + 这通常意味着你需要将构建指令封装在一个首先查询对象是否存在的接口之后。像这样隐藏构造指令是工厂模式的一个例子。 + + * In order to return a previously created flyweight, you'll have to keep track + of the pool of ones that you've already instantiated. As the name implies, + that means that an Object + Pool might be a helpful place to store them. + + * 为了返回一个已经创建的享元,你需要追踪那些你已经实例化的对象池。就像名字暗示的那样,这就意味着对象池是一个存储他们的好地方。 + + * When you're using the State + pattern, you often have "state" objects that don't have any fields specific + to the machine that the state is being used in. The state's + identity and methods are enough to be useful. In that case, you can apply + this pattern and reuse that same state instance in multiple state machines + at the same time without any problems. + + * 当你使用状态模式的时候,你通常有不具有任何特定字段的“状态对象”。这个状态的身份和方法都足够有用。在这种情况下,你可以使用这个模式,然后在不同的状态机上使用相同的对象实例。 diff --git a/book/game-loop.markdown b/book/game-loop.markdown new file mode 100644 index 0000000..d8d9630 --- /dev/null +++ b/book/game-loop.markdown @@ -0,0 +1,934 @@ +^title Game Loop +^section Sequencing Patterns + +## Intent + +## 意图 + +*Decouple the progression of game time from user input and processor speed.* + +*将游戏的处理过程与玩家输入和处理器速度解耦。* + +## Motivation + +## 动机 + +If there is one pattern this book couldn't live without, this is it. Game loops +are the quintessential example of a "game programming pattern". Almost every +game has one, no two are exactly alike, and relatively few programs outside of +games use them. + +如果这本书有一个不可或缺模式,那就是这个了。游戏循环是“游戏设计模式”的精华例子。几乎每一个游戏都有一个,没有两个是相同的,而在游戏以外机会没有程序使用它。 + +To see how they're useful, let's take a quick trip down memory lane. In the +olden days of computer programming when everyone had beards, programs worked like your dishwasher. You dumped a +load of code in, pushed a button, waited, and got results out. Done. These were +*batch mode* programs -- once the work was done, the program stopped. + +为了看看他们是多有用,让我们快速检视一下内存小巷。这计算机程序的早先日子,每个人都长着胡子,程序像你的洗碗机一样工作。你输入一堆代码,按个按钮,等待,然后获得结果,完成。这些都是*批处理*程序,一旦工作完成,程序就停止了。 + + + +You still see these today, though thankfully we don't have to write them on +punch cards anymore. Shell scripts, command line programs, and even the little +Python script that turns a pile of Markdown into this book are all batch mode +programs. + +你在今日仍然能看到这恶心,虽然感谢上天我们不必在打孔纸上面编写他们了。终端脚本,命令行程序,甚至将Markdown翻译成这本书的Python脚本都是批处理程序。 + +### Interview with a CPU + +### 采访CPU + +Eventually, programmers realized having to drop off a batch of code at the +computing office and come back a few hours later for the results was a terribly +slow way to get the bugs out of a program. They wanted immediate feedback. +*Interactive* programs were born. Some of the first interactive programs were +games: + +最终,程序意识到将批处理代码留在计算办公室,然后几个小时后获得结果是一个糟糕的慢方法来找出程序的漏洞。他们想要立即的反馈。*交互式*程序诞生了。第一批交互式程序中的一些事游戏: + + + + YOU ARE STANDING AT THE END OF A ROAD BEFORE A SMALL BRICK + BUILDING . AROUND YOU IS A FOREST. A SMALL + STREAM FLOWS OUT OF THE BUILDING AND DOWN A GULLY. + + > GO IN + YOU ARE INSIDE A BUILDING, A WELL HOUSE FOR A LARGE SPRING. + + + +You could have a live conversation with the program. It waited for your input, +then it would respond to you. You would reply back, taking turns just like you +learned to do in kindergarten. When it was your turn, it sat there doing +nothing. Something like: + +你可以和这个程序进行一些交流。他等待你的熟人,然后它会回应你。你再回复,就像你再幼儿园中学到的那样轮流。当你的回合,他坐在那里啥也不做。像这样: + + + +^code 1 + + + +### Event loops + +### 事件循环 + +Modern graphic UI applications are surprisingly similar to old adventure games +once you shuck their skin off. Your word processor usually just sits there doing +nothing until you press a key or click something: + +如果你剥开现代的图形UI的外皮,会惊讶地发现他们与老旧的冒险游戏差不多。你的文本处理器通常呆在那里什么也不做直到你按了个键或者点击了什么东西: + +^code 2 + +The main difference is that instead of *text commands*, the program is waiting +for *user input events* -- mouse clicks and key presses. It still works +basically like the old text adventures where the program *blocks* waiting for user input, which is a problem. + +最大的不同是,不使用*文本命令*,程序等待*用户输入事件*——鼠标点击和按键按下。他还是和以前的老式文本冒险游戏一样,程序*阻塞*在等待用户输入,这是一个问题。 + +Unlike most other software, games keep moving even when the user isn't providing +input. If you sit staring at the screen, the game doesn't freeze. Animations +keep animating. Visual effects dance and sparkle. If you're unlucky, that +monster keeps chomping on your hero. + +不想大多数其他软件,游戏即使在玩家没有输入时也在继续运行。入股你站在那里看着屏幕,游戏不会冻结。动画继续动着。视觉效果跳跃闪烁。如果你不幸的话,怪物继续咬噬你的英雄。 + + + +This is the first key part of a real game loop: *it processes user input, but +doesn't wait for it*. The loop always keeps spinning: + +这是游戏循环的第一个关键部分:*他处理用户输入,但是不等待它*。循环总是继续旋转: + +^code 3 + +We'll refine this later, but the basic pieces are here. `processInput()` handles +any user input that has happened since the last call. Then, `update()` advances the game simulation one step. It runs +AI and physics (usually in that order). Finally, `render()` draws the game so +the player can see what happened. + +我们之后会改善它,但是基本部分都在这里了。`processInput()`处理上次调用到现在的任何玩家输入。然后`update()`运行游戏模拟的下一步。这运行了AI和物理(通常是这种顺序)。最终,`render()`绘制游戏这样玩家可以看到发生了什么。 + + + +### A world out of time + +### 时间之外的世界 + +If this loop isn't blocking on input, that leads to the obvious question: how +*fast* does it spin? Each turn through the game loop advances the state of the +game by some amount. From the perspective of an inhabitant of the game world, +the hand of their clock has ticked forward. + +如果这个循环不是在输入中阻塞,这就带来了一个明显的问题,要转*多快*?么每次穿过游戏循环都会发展一定的游戏状态。从游戏世界的居民看来,他们手上的表就会滴答一下。 + + + +Meanwhile, the *player's* actual clock is ticking. If we measure how quickly the +game loop cycles in terms of real time, we get the game's "frames per second". +If the game loop cycles quickly, the FPS is high and the game moves smoothly and +quickly. If it's slow, the game jerks along like a stop motion movie. + +同时,*玩家的*真实手表也在滴答着。如果我们用实际时间来测算游戏循环运行的速度,我们得到了游戏的“帧每秒”。如果游戏循环的更快,FPS就更高,游戏运行的更流畅更快。若干他很慢,游戏看上去就像是慢动作电影。 + +With the crude loop we have now where it just cycles as quickly as it can, two +factors determine the frame rate. The first is *how much work it has to do each +frame*. Complex physics, a bunch of game objects, and lots of graphic detail all +will keep your CPU and GPU busy, and it will take longer to complete a frame. + +我们现在写的这个循环可以能转多快转多快,两个因素决定了帧率。第一个是每帧要做多少工作。复杂物理,一对游戏对象,很多图形细节购汇让你的CPU和GPU繁忙,这决定了需要多久完成一帧。 + +The second is *the speed of the underlying platform.* Faster chips churn through +more code in the same amount of time. Multiple cores, GPUs, dedicated audio +hardware, and the OS's scheduler all affect how much you get done in one tick. + +第二个是*潜在平台的速度。*更快的芯片可以在同样的回家按运行更多的嗲吗。多核,GPU组,独立声卡,以及系统的调度都影响了在一次滴答中能够做多少东西。 + +### Seconds per second + +### 每秒的秒数 + +In early video games, that second factor was fixed. If you wrote a game for the +NES or Apple IIe, you knew *exactly* what CPU your game was running on and you +could (and did) code specifically for that. All you had to worry about was how +much work you did each tick. + +在早期的视频游戏中,第二个参数是固定的。如果你为NES或者Apple IIe写游戏,你*明确*知道你的游戏运行在什么CPU上你可以(而且确实)为他特别的写代码。你需要担忧的是每一次滴答要做多少工作。 + +Older games were carefully coded to do just enough work each frame so that the +game ran at the speed the developers wanted. But if you tried to play that same +game on a faster or slower machine, then the game +itself would speed up or slow down. + +老些的游戏被仔细地编码,一帧只做足够的工作,这样游戏可以以开发者想要的速率运行。但是如果你想要在快一些或者慢一些的机器上运行同一个游戏,游戏速度本身就会加速或者减速。 + + + +These days, though, few developers have the luxury of knowing exactly what +hardware their game will run on. Instead, our games must intelligently adapt to +a variety of devices. + +这些天,很少有开发者可以奢侈的知道他们游戏需要运行的硬件条件。相反,我们的游戏必须自动适应多种设备。 + +This is the other key job of a game loop: *it runs the game at a consistent +speed despite differences in the underlying hardware.* + +这就是游戏循环的另一个关键工作:*不管潜在硬件的条件,他在一个固定的速度运行。* + +## The Pattern + +## 模式 + +A **game loop** runs continuously during gameplay. Each turn of the loop, it +**processes user input** without blocking, **updates the game state**, and +**renders the game**. It tracks the passage of time to **control the rate of +gameplay**. + +一个**游戏循环**在游玩中不断运行。循环的每一回合,他无阻塞地**处理玩家的输入**,**更新游戏状态**,**渲染游戏**。他追踪时间的消息来**控制游玩的速度。** + +## When to Use It + +## 何时使用 + +Using the wrong pattern can be worse than using no pattern at all, so this +section is normally here to caution against over-enthusiasm. The goal of design +patterns isn't to cram as many into your codebase as you can. + +使用错误的模式比不使用模式更糟,所以这节通常是要小心过于热衷。设计模式的目标不是往代码库里尽可能的塞东西。 + +But this pattern is a bit different. I can say with pretty good confidence that +you *will* use this pattern. If you're using a game engine, you won't write it yourself, but it's still there. + +但是这个模式有所不同。我可以很自信的说你*会*使用这个模式。如果你使用一个游戏引擎,你不需要主角写,但是它还在那里。 + + + +You might think you won't need this if you're making a turn-based game. But even +there, though the *game state* won't advance until the user takes their turn, +the *visual* and *audible* states of the game usually do. Animation and music +keep running even when the game is "waiting" for you to take your turn. + +你可能认为你在做回合制游戏的时候不需要这个。但是哪怕是那里,就算*游戏状态*到玩家回合才改变,*视觉*和*听觉*状态仍会改变。动画和音乐会继续运行,哪怕游戏在“等待”你进行你的回合。 + +## Keep in Mind + +## 记住 + +The loop we're talking about here is some of the most important code in your +game. They say a program spends 90% of its time in +10% of the code. Your game loop will be firmly in that 10%. Take care with this +code, and be mindful of its efficiency. + +我们这里谈到的循环是你游戏代码中最重要的部分。他们说一个程序会花费90%的事件在10%的代码上。你的游戏循环代码肯定在这10%中。好好对待代码,然后注意他的效率。 + + + +### You may need to coordinate with the platform's event loop + +### 你需要与平台的事件循环协调 + +If you're building your game on top of an OS or platform that has a graphic UI +and an event loop built in, then you have *two* application loops in play. They'll +need to play nice together. + +如果你在操作系统的顶层或者有图形UI和内建的事件循环的平台,那你就有了两个应用循环在运作。他们需要很好的配合。 + +Sometimes, you can take control and make your loop the only one. For +example, if you're writing a game against the venerable Windows API, your +`main()` can just have a game loop. Inside, you can call `PeekMessage()` to +handle and dispatch events from the OS. Unlike `GetMessage()`, `PeekMessage()` +doesn't block waiting for user input, so your game loop will keep cranking. + +有时候,你可以控制让你之循环其中一个。举个例子,如果用舍弃了Windows的珍贵API,你的`main()`可以只有一个游戏循环。在里面,你可以调用`PeekMessage()`来处理和分发系统的事件。不像`GetMessage()`,`PeekMessage()`没有阻塞等待用户输入,因此你的游戏循环会保持运作。 + +Other platforms don't let you opt out of the event loop so easily. If you're +targeting a web browser, the event loop is deeply built into browser's execution +model. There, the event loop will run the show, and you'll use it as your game +loop too. You'll call something like `requestAnimationFrame()` and it will +call back into your code to keep the game running. + +其他的平台不会让你这么轻松的选择事件循环。如果你的目标是一个网页浏览器,事件徐汇被内建在浏览器的执行模型的深处。这样,你会用事件循环和游戏循环。你会调用`requestAnimationFrame()`之类的他会返回你的代码让游戏继续运行。 + +## Sample Code + +## 示例代码 + +For such a long introduction, the code for a game loop is actually pretty +straightforward. We'll walk through a couple of variations and go over their +good and bad points. + +对于一个如此长的介绍,游戏循环的代码实际上很直观。我们会浏览一堆变种并他们的好处和坏处。 + +The game loop drives AI, rendering, and other game systems, but those aren't the +point of the pattern itself, so we'll just call into fictitious methods here. +Actually implementing `render()`, `update()` and others is left as a +(challenging!) exercise for the reader. + +游戏循环驱动了AI,渲染和其他游戏系统,但这些不是模式的要点,所以我们会调用虚拟的方法。在实际实现了`render()`,`update()`,剩下的作为一个(挑战性的)练习给读者。 + +### Run, run as fast as you can + +### 跑,能跑多快跑多快 + +We've already seen the simplest possible game loop: + +我们已经看到了可能是最简单的游戏循环: + +^code 3 + +The problem with it is you have no control over how fast the game runs. On a +fast machine, that loop will spin so fast users won't be able to see what's +going on. On a slow machine, the game will crawl. If you have a part of the game +that's content-heavy or does more AI or physics, the game will actually play +slower there. + +它的问题是你不能控制游戏运行的有多快。在一个快速机器上,循环会运行的如此之快以至于玩家看不清发生了什么。在慢速机器上,游戏在爬行。如果游戏的一部分时有大量内容或者做了很多AI或物理,游戏就会在那里慢一些。 + +### Take a little nap + +### 休息一下 + +The first variation we'll look at adds a simple fix. Say you want your game to +run at 60 FPS. That gives you about 16 milliseconds per +frame. As long as you can reliably do all of your game processing and rendering +in less than that time, you can run at a steady frame rate. All you do is process +the frame and then *wait* until it's time for the next one, like so: + +我们看到的第一个问题可以增加一个简单的修复。假设你想要你的游戏一60FPS运行。这就给了你大约每帧16毫秒。只要你可以用少于这个时间可靠地处理所有的游戏内容和渲染,你就可以以一个稳定的帧率运行。你需要做的就是处理这一帧然后*等待*知道是时候处理下一个,就像这样: + +A simple game loop flowchart. Process Input → Update Game → Render → Wait, then loop back to the beginning. + +The code looks a bit like this: + +代码看上去像这样: + + + +^code 4 + +The `sleep()` here makes sure the game doesn't run too *fast* if it processes a +frame quickly. It *doesn't* help if your game runs too *slowly*. If it takes +longer than 16ms to update and render the frame, your sleep time goes +*negative*. If we had computers that could travel back in time, lots of things +would be easier, but we don't. + +这里的`sleep()`保证了游戏不会运行太*快*如果它很快的处理一帧。这无法帮忙如果你的游戏运行太*慢*。如果需要超过16ms来更新并渲染一帧,你休眠的时间就变成了*负的*。如果我们的计算机可以回退时间,很多事情就很容易了,但是不可以。 + +Instead, the game slows down. You can work around this by doing less work each +frame -- cut down on the graphics and razzle dazzle or dumb down the AI. But that +impacts the quality of gameplay for all users, even ones on fast machines. + +相反,游戏变慢了。你可以通过每帧少做一些工作来解决这个问题——减少物理效果和绚丽光影,或者把AI变笨。但是这影响了玩家的游戏体验,哪怕是那些在快速机器上的。 + +### One small step, one giant step + +### 一小步,一大步 + +Let's try something a bit more sophisticated. The problem we have basically +boils down to: + +让我们尝试一些更加复杂的东西。我们拥有的问题基本上是: + + 1. Each update advances game time by a certain amount. + + 2. It takes a certain amount of *real* time to process that. + + 1. 每一次更新将游戏时间推动一个固定量。 + + 2. 这消耗一定量的*真实*时间来处理它。 + +If step two takes longer than step one, the game slows down. If it takes more +than 16 ms of processing to advance game time by 16ms, it can't possibly keep +up. But if we can advance the game by *more* than 16ms of game time in a single +step, then we can update the game less frequently and still keep up. + +如果第二步消耗的时间超过第一步,游戏变慢了。如果它需要超过16ms来来推动游戏时间16ms,它永远跟不上。但是如果我们一步中推动游戏时间*超过*16ms,纳米我们可以减少更新的频率仍然可以跟得上。 + +The idea then is to choose a time step to advance based on how much *real* time +passed since the last frame. The longer the frame takes, the bigger steps the +game takes. It always keeps up with real time because it will take bigger and +bigger steps to get there. They call this a *variable* or *fluid* time step. It +looks like: + +之后的主意是选择一个事件间隔基于上一帧到现在有多少*真实*时间过去了。这一帧花费的时间越长,游戏的间隔越大。它总能跟上真实时间,因为它走的步子越来越大。他们称之为*变化的*或者*流动的*时间间隔。它看上去像是: + +^code 5 + +Each frame, we determine how much *real* time passed since the last game update +(`elapsed`). When we update the game state, we pass that in. The engine is then +responsible for advancing the game world forward by that amount of time. + +每一帧,我们决定上一次游戏更新(`elapsed`)到现在有多少时间过去了。当我们更新游戏状态时,我们将其传入。游戏引擎复杂退浆游戏世界一个确定的时间量。 + +Say you've got a bullet shooting across the screen. With a fixed time step, in each +frame, you'll move it according to its velocity. With a variable time step, you +*scale that velocity by the elapsed time*. As the time step gets bigger, the +bullet moves farther in each frame. That bullet will get across the screen in +the *same* amount of *real* time whether it's twenty small fast steps or four +big slow ones. This looks like a winner: + +假设你有一颗子弹跨过屏幕。使用一个固定的时间间隔,在每一帧中,你根据他的速度移动它。使用一个变化的时间间隔,你*根据过去的时间决定速度的比例*。随着时间间隔加大,子弹在每一帧间移动的更远。那个子弹在*真实*时间里移动*同样*多的距离,无论是二十个快的小间隔还是四个慢的大间隔。这看上去是胜利者: + + * The game plays at a consistent rate on different hardware. + + * 游戏在不同 的硬件上以固定的速度运行。 + + * Players with faster machines are rewarded with smoother gameplay. + + *使用更快机器的玩家获得了更流畅的游戏体验。 + +But, alas, there's a serious problem lurking ahead: we've made the game non-deterministic and unstable. Here's one example +of the trap we've set for ourselves: + +但是,悲哀,这里有一个严重的问题:我们让游戏不再是确定性的了也不再稳定。这里是我们给自己挖的一个坑: + + + +Say we've got a two-player networked game and Fred has some beast of a gaming +machine while George is using his grandmother's antique PC. That aforementioned +bullet is flying across both of their screens. On Fred's machine, the game is +running super fast, so each time step is tiny. We cram, like, 50 frames in the +second it takes the bullet to cross the screen. Poor George's machine can only fit in about +five frames. + +假设我们有一个双人联网游戏,Fred有一台游戏性能猛兽机,而George正在使用at祖母的老爷机。前面提到的子弹在他们两人的屏幕上飞行。在Fred的机器上,游戏跑的超级快,每一个时间间隔都很小。我们塞了,比如,50帧在子弹穿过屏幕的那一秒。可怜的George的机器只能跑到大约5帧。 + +This means that on Fred's machine, the physics engine updates the bullet's position +50 times, but George's only does it five times. Most games use floating point +numbers, and those are subject to *rounding error*. Each time you add two +floating point numbers, the answer you get back can be a bit off. Fred's machine +is doing ten times as many operations, so he'll accumulate a bigger error than +George. The *same* bullet will end up in *different places* on their machines. + +这就意味着在Fred 的机器上,物理引擎每秒更新50次位置,但是George的只能做5次。大多数游戏使用浮点数,他们有*每轮误差*。每一次你讲两个浮点数加在一起,你获得的结果就会有一点偏差。Fred的机器做了10倍多的操作,所以它的误差要比George的更大。*同样*的子弹最终在他们的机器上到了*不同的位置*。 + +This is just one nasty problem a variable time step can cause, but there are more. +In order to run in real time, game physics engines are approximations of the +real laws of mechanics. To keep those approximations from blowing up, damping is applied. That damping is carefully +tuned to a certain time step. Vary that, and the physics gets unstable. + +这是我们使用变化的时间可以引起的一个问题,但是还有更多。为了在真实时间运行,游戏图形引擎是实际机制法则的近似值。为了从爆炸计算这些近似值,阻尼被添加了。这个阻尼每次时间间隔被小心的调整。改变它,物理就不再稳定。 + + + +This instability is bad enough that this example is only here as a cautionary +tale and to lead us to something better... + +这种不稳定性太糟了,这个例子在这里的唯一原因是作为警示寓言,带领我们到更好的东西…… + +### Play catch up + +### 追逐游戏 + +One part of the engine that usually *isn't* affected by a variable time step is +rendering. Since the rendering engine captures an +instant in time, it doesn't care how much time advanced since the last one. It +renders things wherever they happen to be right then. + +我们游戏中通常*不会*被动态时间间隔影响到的是渲染。由于渲染引擎捕捉一个时间上的瞬间,它不再会从上一次到现在过了多久。它渲染事物在所在的地方。 + + + +We can use this fact to our advantage. We'll *update* the game using a fixed +time step because that makes everything simpler and more stable for physics and +AI. But we'll allow flexibility in when we *render* in order to free up some +processor time. + +我们可以利用这点作为优势。我们用固定的时间间隔*更新*游戏,因为这让所有事情变得简单,对于物理和AI也更加稳定。但是我们允许*渲染*上的灵活性来释放一些处理器时间。 + +It goes like this: A certain amount of real time has elapsed since the last turn +of the game loop. This is how much game time we need to simulate for the game's +"now" to catch up with the player's. We do that using a *series* of *fixed* time +steps. The code looks a bit like: + +它像这样运作:固定量的时间自上一次游戏循环逝去。这就是我们需要为游戏的“现在”模拟的来追上玩家的时间。我们使用一*系列*的*固定*时间间隔。代码看上去像是这样的: + +^code 6 + +There's a few pieces here. At the beginning of each frame, we update `lag` based +on how much real time passed. This measures how far the game's clock is behind +compared to the real world. We then have an inner loop to update the game, one +fixed step at a time, until it's caught up. Once we're caught up, we render and +start over again. You can visualize it sort of like this: + +这里还有几个小块。在每一帧的开头,我们基于有多少真实时间过去更新`lag`。这测量了游戏世界是时钟比真实世界落后了多少,直到追上。一旦我们追上,我们就渲染然后重新开始。你可以将这种视觉化如下: + +A modified flowchart. Process Input → Update Game → Wait, then loop back to this step then → Render → Loop back to the beginning. + +Note that the time step here isn't the *visible* frame rate anymore. +`MS_PER_UPDATE` is just the *granularity* we use to update the game. The shorter +this step is, the more processing time it takes to catch up to real time. The +longer it is, the choppier the gameplay is. Ideally, you want it pretty short, +often faster than 60 FPS, so that the game simulates with high fidelity on fast +machines. + +注意这里的时间间隔不是*可见的*帧率了。`MS_PER_UPDATE`只是我们更新游戏的*间隔*。这个间隔越短,需要更多的处理时间来追上真实时间。它越长,游戏抖动的越厉害。理想上,你想要它特别短,通常快过60FPS,所以游戏子啊快速机器上会有高效的表现。 + +But be careful not to make it *too* short. You need to make sure the time step +is greater than the time it takes to process an `update()`, even on the slowest hardware. Otherwise, your game simply can't catch up. + +但是小心不要把它整的*太*短了。你需要保证这个事件间隔长于需要处理一个`update()`,即使在最慢的硬件上。否则,你的游戏就跟不上了。 + + + +Fortunately, we've bought ourselves some breathing room here. The trick is that +we've *yanked rendering out of the update loop*. That frees up a bunch of CPU +time. The end result is the game *simulates* at a constant rate using safe fixed +time steps across a range of hardware. It's just that the player's *visible +window* into the game gets choppier on a slower machine. + +幸运的是,我们给自己弄出了一些呼吸的空间。技巧是我们将*渲染拉出了更新循环*。这释放了很大一部分CPU时间。结果是游戏以固定速率*模拟*,使用了对于很多硬件安全的固定时间间隔。这只是玩家的*视觉窗口*在慢速机器上会有抖动。 + +### Stuck in the middle + +### 卡在中间 + +There's one issue we're left with, and that's residual lag. We update the game +at a fixed time step, but we render at arbitrary points in time. This means that +from the user's perspective, the game will often display at a point in time +between two updates. + +我们还剩一个问题,就是剩下的延迟。我们以固定的时间间隔更新游戏,但是我们在任意时间渲染。这就意味着从玩家的角度看,游戏经常在两次更新之间的时候显示。 + +Here's a timeline: + +这是时间线: + +A timeline containing evenly spaced Updates and intermittent Renders. + +As you can see, we update at a nice tight, fixed interval. Meanwhile, we render +whenever we can. It's less frequent than updating, and it isn't steady either. Both +of those are OK. The lame part is that we don't always render right at the point +of updating. Look at the third render time. It's right between two updates: + +就像你看到的那样,我们以一个紧凑固定的间隔更新。同时,我们在任何可以的时候渲染。它比更新发生的要少,二期它也不是稳定的。两者都是没问题的。糟糕的是,我们不总是能在更新的正确时间点渲染。看看第三次渲染时间。他在两次更新之间。 + +Close-up of the timeline showing Renders falling between Update steps. + +Imagine a bullet is flying across the screen. On the first update, it's on the +left side. The second update moves it to the right side. The game is rendered at +a point in time between those two updates, so the user expects to see that +bullet in the center of the screen. With our current implementation, it will +still be on the left side. This means motion looks jagged or stuttery. + +想象一个子弹飞过屏幕。在第一次更新中,它在左边。第二次更新将它移到了右边。这个游戏在两次更新间的特定时间点渲染,所以玩家期待看到子弹在屏幕的中间。而我们现在的实现中,它还在左边。这就意味着移动看上去是锯齿状的。 + +Conveniently, we actually know *exactly* how far between update frames we are +when we render: it's stored in `lag`. We bail out of the update loop when it's +less than the update time step, not when it's *zero*. That leftover amount? +That's how far into the next frame we are. + +方便的是,我们实际知道渲染时距离两帧的时间:它被存储在`lag`中。我们在它比更新时间间隔小的时候取出它,而不是它是*零*的时候。而剩余量?这就是到下一帧的时间。 + +When we go to render, we'll pass that in: + +当我们要渲染时,我们会将其传入: + + + +^code 7 + + + +The renderer knows each game object *and its current velocity*. Say that bullet +is 20 pixels from the left side of the screen and is moving right 400 pixels per +frame. If we are halfway between frames, then we'll end up passing 0.5 to +`render()`. So it draws the bullet half a frame ahead, at 220 pixels. Ta-da, +smooth motion. + +渲染器知道每一个游戏对象*以及他现在的速度*。假设子弹在屏幕左边20像素的地方,正在以400像素每帧的速度向右移动。如果我们在两帧中间,我们会传0.5给`render()`。所以它绘制了半帧之前,在220像素,Ta-da,平滑移动。 + +Of course, it may turn out that that extrapolation is wrong. When we +calculate the next frame, we may discover the bullet hit an obstacle or slowed +down or something. We rendered its position interpolated between where it was on +the last frame and where we *think* it will be on the next frame. But we don't +know that until we've actually done the full update with physics and AI. + +当让,也许这种推断是错误的。但我们计算下一帧的时候,我们也许会发现子弹碰撞到另一个障碍或者减速或者别的什么。我们在它上一帧和我们*认为*它在下一帧的位置之间插值。但是我们在完成完整的物理和AI更新后才能真的知道。 + +So the extrapolation is a bit of a guess and sometimes ends up wrong. +Fortunately, though, those kinds of corrections usually aren't noticeable. At +least, they're less noticeable than the stuttering you get if you don't +extrapolate at all. + +所以推断有猜测的成分,有时候结果是错误的。但是,幸运的,这种修正通常不可感知。最起码,他们比你不使用推断导致的结巴更不明显。 + +## Design Decisions + +## 设计决策 + +Despite the length of this chapter, I've left out more than I've included. Once +you throw in things like synchronizing with the display's refresh rate, +multithreading, and GPUs, a real game loop can get pretty hairy. At a high +level, though, here are a few questions you'll likely answer: + +不管这一章的长度,我剩下的超过我包含的。一旦你将显示的刷新频率,多线程,多GPU,加到里面,一个真正的游戏循环就会变得毛绒绒的。在高层中,这里还有一些问题你需要回答: + +### Do you own the game loop, or does the platform? + +### 你拥有游戏循环,还是平台? + +This is less a choice you make and more one that's made for you. If you're +making a game that runs in a web browser, you pretty much *can't* write your own +classic game loop. The browser's event-based nature precludes it. Likewise, if +you're using an existing game engine, you will probably rely on its game loop +instead of rolling your own. + +与其说这是一个你的选择,不如说有人为你选了。如果你在做一个浏览器中的游戏,你很可能*不能*写你自己的经典游戏循环。浏览器本身的事件驱动阻碍了这一点。同样,如果你是用来一个现存的游戏引擎,你很可能依赖于它的游戏循环而不是自己写一个。 + + * **Use the platform's event loop:** + + * **使用平台的事件循环:** + + * *It's simple.* You don't have to worry about writing and optimizing the + core loop of the game. + + * *简单*你不必担心写和优化自己的游戏核心循环。 + + * *It plays nice with the platform.* You don't have to worry about + explicitly giving the host time to process its own events, caching + events, or otherwise managing the impedance mismatch between the + platform's input model and yours. + + * *平台友好。*你不必担心明确的给时间去处理它自己的事件,缓存事件,或者管理任何平台输入模型和你的不同之处。 + + * *You lose control over timing.* The platform will call your code as it + sees fit. If that's not as frequently or as smoothly as you'd like, too + bad. Worse, most application event loops weren't designed with games in + mind and usually *are* slow and choppy. + + * *你失去了对时间的控制。*平台会在它合适的时候调用你的代码。如果这不如你想要的平滑或者频繁,太糟了。更早的是,大多数应用的事件循环没有为游戏设计通常*是*又慢又抖动。 + + * **Use a game engine's loop:** + + * **使用游戏引擎的循环:** + + * *You don't have to write it.* Writing a game loop can get pretty tricky. + Since that core code gets executed every frame, minor bugs or + performance problems can have a large impact on your game. A tight game + loop is one reason to consider using an existing engine. + + * *你不必自己写。*写一个游戏循环可能会非常有技巧。友谊那是每帧都要执行的核心代码,小小的漏洞或者性能问题就可以对你的游戏有巨大的影响。一个紧密的游戏循环是一个使用现有引擎的原因。 + + * *You don't get to write it.* Of course, the flip side to that coin is + the loss of control if you *do* have needs that aren't a perfect fit for + the engine. + + * *你不必自己写。*当然,硬币的另一面是逝去了控制,如果你*确实*需要的对引擎不是最好的。 + + * **Write it yourself:** + + * **自己写:** + + * *Total control.* You can do whatever you want with it. You can design it + specifically for the needs of your game. + + * *完全的控制。*你可以做任何你想做的事情。你可以做任何你想做的事情。你可以为你游戏的需求特定。 + + * *You have to interface with the platform.* Application frameworks and + operating systems usually expect to have a slice of time to process + events and do other work. If you own your app's core loop, + it won't get any. You'll have to explicitly hand off control + periodically to make sure the framework doesn't hang or get confused. + + * *你需要与平台交互。*引用框架和操作系统通常有时间片去处理事件和做其他工作。如果你拥有你应用的核心循环,它不会有。你得明确地定期检查保证框架没有挂起或者糊涂。 + +### How do you manage power consumption? + +### 你如何管理性能消耗? + +This wasn't an issue five years ago. Games ran on things plugged into walls or on +dedicated handheld devices. But with the advent of smartphones, laptops, and +mobile gaming, the odds are good that you do care about this now. A game that runs +beautifully but turns players' phones into space heaters before running out of +juice thirty minutes later is not a game that makes people happy. + +这在五年前还不是问题。游戏运行在插到插座上的或者专用的设备上。但是随着智能手机,笔记本以及移动游戏的发展,这些古怪的东西现在需要关注了。一个游戏完美运行但是会用完手机三十分钟前充的电,并变成空间加热器,这可不是一个能让人民开心的游戏。 + +Now, you may need to think not only about making your game look great, but also use as +little CPU as possible. There will likely be an *upper* bound to performance +where you let the CPU sleep if you've done all the work you need to do in a +frame. + +现在,你需要不仅仅考虑让你的游戏看上去焊好,同时尽可能少的使用CPU。如果你让所有你需要的工作在一帧之内完成后CPU睡眠会对性能造成一个*上限*。 + + * **Run as fast as it can:** + + * **尽可能快的运行:** + + This is what you're likely to do for PC games (though even those are + increasingly being played on laptops). Your game loop will never explicitly + tell the OS to sleep. Instead, any spare cycles will be spent cranking up + the FPS or graphic fidelity. + + 这就是你通常想让PC游戏做的事情(即使有不断增加数目实在笔记本上运行的)。你的游戏循环永远不会显式告诉系统休眠。相反,一个空闲的循环被划在提升FPS或者图像显示上了。 + + This gives you the best possible gameplay experience but, it will use as much + power as it can. If the player is on a laptop, they'll have a nice lap + warmer. + + 这会给你最好可能的游戏体验但是,他会尽可能多的使用电量。如果玩家在笔记本上玩,他们会有一个很好的笔记本加热器。 + + * **Clamp the frame rate:** + + * **固定帧率** + + Mobile games are often more focused on the quality of gameplay than they are + on maximizing the detail of the graphics. Many of these games will set an upper + limit on the frame rate (usually 30 or 60 FPS). If the game loop is done + processing before that slice of time is spent, it will just sleep for the + rest. + + 移动游戏更加注意游戏体验质量,而不是他们图形细节的最大化。很多这种游戏都会设置一个最大帧率(通常是30或60FPS)。如果游戏循环在分配的时间片消耗完之前完成了,他会在剩余的时间休眠。 + + This gives the player a "good enough" experience and then goes easy on their + battery beyond that. + + 这给了玩家“足够好的”游戏体验,也让背后的电池放松了一点。 + +### How do you control gameplay speed? + +### 你如何控制游玩速度? + +A game loop has two key pieces: non-blocking user input and adapting to the +passage of time. Input is straightforward. The magic is in how you deal with +time. There are a near-infinite number of platforms +that games can run on, and any single game may run on quite a few. How it +accommodates that variation is key. + +一个游戏循环有两个关键部分:不阻塞的用户输入和对通过时间的适应。输入很直观。魔力之处在于你如何处理事件。这里有数不尽的游戏可运行的平台,每一个游戏都能在其中一些上运行。对变化的适应性如何就是关键。 + + + + * **Fixed time step with no synchronization:** + + * **固定的事件间隔没有同步:** + + This was our first sample code. You just run the game loop as fast as you + can. + + 这是我们第一个样例代码。你只需尽可能的快运行游戏。 + + * *It's simple*. This is its main (well, only) virtue. + + * *简单*。这是主要的(好吧,唯一的)好处。 + + * *Game speed is directly affected by hardware and game complexity.* And + its main vice is that if there's any variation, it will directly affect + the game speed. It's the fixie of game loops. + + * *游戏速度直接与硬件和游戏复杂度相关。*他主要的缺点是如果有变化,它会直接影响游戏速度。它与游戏循环咬死了。 + + * **Fixed time step with synchronization:** + + * **有同步的固定时间间隔:** + + The next step up on the complexity ladder is running the game at a fixed + time step but adding a delay or synchronization point at the end of the + loop to keep the game from running too fast. + + 下一步对复杂度控制是使用一个固定的时间间隔但是在循环的末尾增加一个同步点保证游戏不会运行的过快。 + + * *Still quite simple.* It's only one line of code more than the + probably-too-simple-to-actually-work example. In most game loops, you + will likely do synchronization *anyway*. You will probably [double + buffer](double-buffer.html) your graphics and synchronize the buffer + flip to the refresh rate of the display. + + * *还是很简单。*这比太简单而不能运行的例子只多了一行代码。在多数游戏循环中,你可能*总*会做一些同步。你可能使用双缓冲你的图形并将你的缓存块与显示更新的频率同步。 + + * *It's power-friendly.* This is a surprisingly important consideration + for mobile games. You don't want to kill the user's battery + unnecessarily. By simply sleeping for a few milliseconds instead of + trying to cram ever more processing into each tick, you save power. + + * *电量友好的。*这是移动游戏惊人重要的一个关注点。你不想不必要的消耗电池电量。通过简单的休眠几个毫秒而不是试图把更多处理塞入每一个滴答,你节约了电量。 + + * *The game doesn't play too fast.* This fixes half of the speed concerns + of a fixed loop. + + * *游戏不会运行的太快。*这解决了固定循环的一半速度问题。 + + * *The game can play too slowly.* If it takes too long to update and render + a game frame, playback will slow down. Because this style doesn't + separate updating from rendering, it's likely to hit this sooner than + more advanced options. Instead of just dropping *rendering* frames to + catch up, gameplay will slow down. + + * *游戏可能运行的太快。*如果花了太多是按去更新和渲染一帧,播放也会减缓。因为这个风格没有分离更新和渲染,它比高级更新系统会更容易碰到这一点。不是扔掉*渲染*的帧来追上,游戏本身会变慢。 + + * **Variable time step:** + + * *动态时间间隔:** + + I'll put this in here as an option in the solution space with the caveat + that most game developers I know recommend against it. It's good to remember + *why* it's a bad idea, though. + + 我把这个放在这里作为问题的解决办法之一,附加警告,大多数我认识的游戏开发者推荐反对它。不过记住*为什么*这是一个坏主意是很有用的。 + + * *It adapts to playing both too slowly and too fast.* If the game can't + keep up with real time, it will just take larger and larger time steps + until it does. + + * *它适应运行太快或者太慢。*如果游戏不能追上真实时间,那就要消耗越来越多的时间间隔直到他跟上。 + + * *It makes gameplay non-deterministic and unstable.* And this is the real + problem, of course. Physics and networking in particular become much + harder with a variable time step. + + * *它让游戏不确定而且不稳定。*这个是真正的问题,当然。特别是物理和网络部分在动态时间间隔会愈发的困难。 + + * **Fixed update time step, variable rendering:** + + * **固定的更新时间间隔,动态的渲染:** + + The last option we covered in the sample code is the most complex, but also + the most adaptable. It updates with a fixed time step, but it can drop + *rendering* frames if it needs to to catch up to the player's clock. + + 我们子啊示例代码中覆盖的最后一个选项是最复杂的,但是也是最有适应性的。它以固定时间间隔更新,但是如果他需要赶上玩家的时钟,它可以扔掉一些*渲染*帧。 + + * *It adapts to playing both too slowly and too fast.* As long as the game + can *update* in real time, the game won't fall behind. If the player's + machine is top-of-the-line, it will respond with a smoother gameplay + experience. + + * *它适应运行太快或者太慢。*只要有心可以在真实时间上*更新*,游戏就不会落后。如果玩家的机器是顶配,它会回应更平滑的游戏体验。 + + * *It's more complex.* The main downside is there is a bit more going on + in the implementation. You have to tune the update time step to be both + as small as possible for the high-end, while not being too slow on the + low end. + + * *更复杂。*这里的主要负面问题是需要在实现中写更多东西。你需要将更新时间间隔调整尽可能小来适应高端机,同时不至于在低端机上太慢。 + +## See Also + +## 参见 + + * The classic article on game loops is Glenn Fiedler's "[Fix Your + Timestep](http://gafferongames.com/game-physics/fix-your-timestep/)". This + chapter wouldn't be the same without it. + + * 关于游戏循环的经典文章是Glenn Fiedler的"[Fix Your Timestep](http://gafferongames.com/game-physics/fix-your-timestep/)"。如果没有这一篇文章,这章就不会是这个样子。 + + * Witters' article on [game + loops](http://www.koonsolo.com/news/dewitters-gameloop/) is a close + runner-up. + + * Witters关于[game loops](http://www.koonsolo.com/news/dewitters-gameloop/)的文章是一个很接近的亚军。 + + * The [Unity](http://unity3d.com/) framework has a complex game loop detailed + in a wonderful illustration [here][unity]. + + * [Unity](http://unity3d.com/)框架有一个复杂的游戏循环,细节在[这里[unity]有完美的解释。 + +[unity]: http://docs.unity3d.com/Manual/ExecutionOrder.html diff --git a/book/introduction.markdown b/book/introduction.markdown new file mode 100644 index 0000000..0ba55e7 --- /dev/null +++ b/book/introduction.markdown @@ -0,0 +1,300 @@ +^title Introduction + +In fifth grade, my friends and I were given access to a little unused +classroom housing a couple of very beat-up TRS-80s. Hoping to inspire us, a +teacher found a printout of some simple BASIC programs for us to tinker with. + +The audio cassette drives on the computers were broken, so any time we wanted to +run some code, we'd have to carefully type it in from scratch. This led us +to prefer programs that were only a few lines long: + + + + 10 PRINT "BOBBY IS RADICAL!!!" + 20 GOTO 10 + + + +Even so, the process was fraught with peril. We didn't know *how* to program, +so a tiny syntax error was impenetrable to us. If the program didn't work, +which was often, we started over from the beginning. + +At the back of the stack of pages was a real monster: a program that took up +several dense pages of code. It took a while before we worked up the courage +to even try it, but it was irresistible -- the title above the listing was +"Tunnels and Trolls". We had no idea what it did, but it sounded like a game, +and what could be cooler than a computer game that you programmed yourself? + +We never did get it running, and after a year, we moved out of that classroom. +(Much later when I actually knew a bit of BASIC, I realized that it was just a +character generator for the table-top game and not a game in itself.) But the +die was cast -- from there on out, I wanted to be a game programmer. + +When I was in my teens, my family got a Macintosh with QuickBASIC and later +THINK C. I spent almost all of my summer vacations +hacking together games. Learning on my own was slow and painful. I'd get +something up and running easily -- maybe a map screen or a little puzzle -- +but as the program grew, it got harder and harder. + + + +At first, the challenge was just getting something working. Then, it became +figuring out how to write programs bigger than what would fit in my head. Instead +of just reading about "How to Program in C++", I started trying to find books +about how to *organize* programs. + +Fast-forward several years, and a friend hands me a +book: *Design Patterns: Elements of Reusable Object-Oriented Software*. +Finally! The book I'd been looking for since I was a teenager. I read it cover +to cover in one sitting. I still struggled with my own programs, but it was +such a relief to see that other people struggled too and came up with +solutions. I felt like I finally had a couple of *tools* to use instead of +just my bare hands. + + + +In 2001, I landed my dream job: software engineer at Electronic Arts. I +couldn't wait to get a look at some real games and see how the pros put them +together. What was the architecture like for an enormous game like Madden +Football? How did the different systems interact? How did they get a single +codebase to run on multiple platforms? + +Cracking open the source code was a humbling and surprising experience. There +was brilliant code in graphics, AI, animation, and visual effects. We had +people who knew how to squeeze every last cycle out of a CPU and put it to +good use. Stuff I didn't even know was *possible*, these people did before +lunch. + +But the *architecture* this brilliant code hung from was often an +afterthought. They were so focused on *features* that organization went overlooked. Coupling was rife between modules. +New features were often bolted onto the codebase wherever they could be made +to fit. To my disillusioned eyes, it looked like many programmers, if they ever +cracked open *Design Patterns* at all, never got past Singleton. + +Of course, it wasn't really that bad. I'd imagined game programmers sitting in +some ivory tower covered in whiteboards, calmly discussing architectural +minutiae for weeks on end. The reality was that the code I was looking at was +written by people working to meet intense deadlines. They did the best they +could, and, as I gradually realized, their best was often very good. The more +time I spent working on game code, the more bits of brilliance I found hiding +under the surface. + +Unfortunately, "hiding" was often a good description. There were gems buried +in the code, but many people walked right over them. I watched coworkers +struggle to reinvent good solutions when examples of exactly what they needed +were nestled in the same codebase they were standing on. + +That problem is what this book aims to solve. I dug up and polished the best +patterns I've found in games, and presented them here so that we can spend our +time inventing new things instead of *re*-inventing them. + +## What's in Store + +There are already dozens of game programming books out there. Why write +another? + +Most game programming books I've seen fall into one of two categories: + +* **Domain-specific books.** These narrowly-focused books give you a deep dive + on some specific aspect of game development. They'll teach you about 3D + graphics, real-time rendering, physics simulation, artificial intelligence, + or audio. These are the areas that many game programmers specialize in as + their careers progress. + +* **Whole-engine books.** In contrast, these try to span all of the different + parts of an entire game engine. They are oriented towards building a + complete engine suited to some specific genre of game, usually a 3D first-person shooter. + +I like both of these kinds of books, but I think they leave some gaps. Books +specific to a domain rarely tell you how that chunk of code interacts with the +rest of the game. You may be a wizard at physics and rendering, but do you +know how to tie them together gracefully? + +The second category covers that, but I often find whole-engine books to be too monolithic and too +genre-specific. Especially with the rise of mobile and casual gaming, we're in +a period where lots of different genres of games are being created. We aren't +all just cloning Quake anymore. Books that walk you through a single engine +aren't helpful when *your* game doesn't fit that mold. + +Instead, what I'm trying to do here is more *à la +carte*. Each of the chapters in this book is an independent idea that +you can apply to your code. This way, you can mix and match them in a way that +works best for the game *you* want to make. + + + +## How it Relates to Design Patterns + +Any programming book with "Patterns" in its name +clearly bears a relationship to the classic *Design Patterns: Elements of +Reusable Object-Oriented Software* by Erich Gamma, Richard Helm, Ralph Johnson, +and John Vlissides (ominously called the "Gang of Four"). + + + +By calling this book "Game Programming Patterns", I'm not trying to imply that +the Gang of Four's book is inapplicable to games. +On the contrary: the [Design Patterns Revisited](design-patterns-revisited.html) +section of this book covers many of the patterns from *Design +Patterns*, but with an emphasis on how they can be applied to game +programming. + +Conversely, I think this book is applicable to non-game software too. I could +just as well have called this book *More Design Patterns*, but I think games +make for more engaging examples. Do you really want to read yet another book +about employee records and bank accounts? + +That being said, while the patterns introduced here are useful in other +software, I think they're particularly well-suited to engineering challenges +commonly encountered in games: + +* Time and sequencing are often a core part of a game's architecture. Things + must happen in the right order and at the right time. + +* Development cycles are highly compressed, and a number of programmers need + to be able to rapidly build and iterate on a rich set of different + behavior without stepping on each other's toes or leaving footprints all + over the codebase. + +* After all of this behavior is defined, it starts interacting. Monsters + bite the hero, potions are mixed together, and bombs blast enemies and + friends alike. Those interactions must happen without the codebase turning + into an intertwined hairball. + +* And, finally, performance is critical in games. Game developers are in a + constant race to see who can squeeze the most out of their platform. + Tricks for shaving off cycles can mean the difference between an A-rated + game and millions of sales or dropped frames and angry reviewers. + +## How to Read the Book + +*Game Programming Patterns* is divided into three broad sections. The first +introduces and frames the book. It's the chapter you're reading now along with +the [next one](architecture-performance-and-games.html). + +The second section, [Design Patterns Revisited](design-patterns-revisited.html), +goes through a handful of patterns from the Gang of Four book. With each chapter, +I give my spin on a pattern and how I think it relates to game programming. + +The last section is the real meat of the book. It presents thirteen +design patterns that I've found useful. They're grouped into four categories: +[Sequencing Patterns](sequencing-patterns.html), [Behavioral Patterns](behavioral-patterns.html), [Decoupling Patterns](decoupling-patterns.html), +and [Optimization Patterns](optimization-patterns.html). + +Each of these patterns is described using a consistent structure so that you +can use this book as a reference and quickly find what you need: + +* The **Intent** section provides a snapshot description of the pattern in + terms of the problem it intends to solve. This is first so that you can hunt + through the book quickly to find a pattern that will help you with your + current struggle. + +* The **Motivation** section describes an example problem that we will be + applying the pattern to. Unlike concrete algorithms, a pattern is usually + formless unless applied to some specific problem. Teaching a pattern without + an example is like teaching baking without mentioning dough. This section + provides the dough that the later sections will bake. + +* The **Pattern** section distills the essence of the pattern out of the + previous example. If you want a dry textbook description of the pattern, + this is it. It's also a good refresher if you're familiar with a pattern + already and want to make sure you don't forget an ingredient. + +* So far, the pattern has only been explained in terms of a single example. + But how do you know if the pattern will be good for *your* problem? + The **When to Use It** section provides some guidelines on when the pattern + is useful and when it's best avoided. The **Keep in Mind** section points + out consequences and risks when using the pattern. + +* If, like me, you need concrete examples to really *get* something, + then **Sample Code** is your section. It walks step by step through a full + implementation of the pattern so you can see exactly how it works. + +* Patterns differ from single algorithms because they are open-ended. Each + time you use a pattern, you'll likely implement it differently. The next section, + **Design Decisions**, explores that space and shows you different options to + consider when applying a pattern. + +* To wrap it up, there's a short **See Also** section that shows how this + pattern relates to others and points you to real-world open source code that + uses it. + +## About the Sample Code + +Code samples in this book are in C++, but that isn't to imply that these +patterns are only useful in that language or that C++ is a better language +for them than others. Almost any language will work fine, though some patterns +do tend to presume your language has objects and classes. + +I chose C++ for a couple of reasons. First, it's the most popular language for +commercially shipped games. It is the *lingua franca* of the industry. Moreso, +the C syntax that C++ is based on is also the basis for Java, C#, JavaScript, +and many other languages. Even if you don't know C++, the odds are good you +can understand the code samples here with a little bit of effort. + +The goal of this book is *not* to teach you C++. The samples are kept as +simple as possible and don't represent good C++ style or usage. Read the code +samples for the idea being expressed, not the code expressing it. + +In particular, the code is not written in "modern" -- C++11 or newer -- style. +It does not use the standard library and rarely uses templates. This makes for +"bad" C++ code, but I hope that by keeping it stripped down, it will be more +approachable to people coming from C, Objective-C, Java, and other languages. + +To avoid wasting space on code you've already seen or that isn't relevant to +the pattern, code will sometimes be omitted in examples. When this occurs, an +ellipsis will be placed in the sample to show where the missing code goes. + +Consider a function that will do some work and then return a value. The +pattern being explained is only concerned with the return value, and not the +work being done. In that case, the sample code will look like: + +^code update + +## Where to Go From Here + +Patterns are a constantly changing and expanding part of software development. +This book continues the process started by the Gang of Four of documenting and +sharing the software patterns they saw, and that process will continue after +the ink dries on these pages. + +You are a core part of that process. As you develop your own patterns and +refine (or refute!) the patterns in this book, you contribute to the software +community. If you have suggestions, corrections, or other feedback about +what's in here, please get in touch! diff --git a/book/object-pool.markdown b/book/object-pool.markdown new file mode 100644 index 0000000..2e1ec64 --- /dev/null +++ b/book/object-pool.markdown @@ -0,0 +1,680 @@ +^title Object Pool +^section Optimization Patterns + +## Intent + +## 意图 + +*Improve performance and memory use by reusing objects from a fixed pool instead +of allocating and freeing them individually.* + +*放弃独立的分配和释放对象,而是从一个固定的池子中重用对象,以提高性能和内存使用* + +## Motivation + +## 动机 + +We're working on the visual effects for our game. When the hero casts a spell, +we want a shimmer of sparkles to burst across the screen. This calls for a +*particle system*, an engine that spawns little sparkly graphics and animates +them until they wink out of existence. + +我们在处理游戏的视觉效果。但英雄发射了一个法术,我们想要闪光在屏幕上爆发。这需要调用*粒子系统*,一个引擎产生动态的闪烁图形,一直显示他们的动画知道他们不再出现。 + +Since a single wave of the wand could cause hundreds of particles to be spawned, +our system needs to be able to create them very quickly. More importantly, we +need to make sure that creating and destroying these particles doesn't cause +*memory fragmentation*. + +由于一次简单的魔杖挥舞就能产生成百上千的粒子,我们的系统需要能够快速制造他们。更重要的是,我们需要保证制造和销毁这些粒子不会造成*内存碎片*。 + +### The curse of fragmentation + +### 碎片的诅咒 + +Programming for a game console or mobile device is closer to embedded +programming than conventional PC programming in many ways. Memory is scarce, +users expect games to be rock solid, and efficient compacting memory managers +are rarely available. In this environment, memory fragmentation is deadly. + +为游戏主机或者移动设备编程在许多方面比为普通的计算机编程更像嵌入式编程。内存紧张,玩家希望游戏是岩石般稳定,有效压缩内存的管理器很难有效。在这种环境下,内存碎片是致命的。 + +Fragmentation means the free space in our heap is broken into smaller pieces of memory instead of one large +open block. The *total* memory available may be large, but the largest +*contiguous* region might be painfully small. Say we've got fourteen bytes free, +but it's fragmented into two seven-byte pieces with a chunk of in-use memory +between them. If we try to allocate a twelve-byte object, we'll fail. No more +sparklies on screen. + +碎片意味着在我们堆中的空余空间被打碎成了很多内存的小碎片而不是一个大的块。*总共的*内存可用也许很大,但是最大的*连续*空间可能痛苦的小。假设我们有十四的空余字节,但是被一块正在使用的内存分割成了两个七字节的碎片。如果我们尝试分配一个十二字节的对象,我们就会失败。屏幕上不会有更多生气。 + + + + + +A series of memory operations leading to fragmentation. + + + +Even if fragmentation is infrequent, it can still gradually reduce the heap to an unusable foam of open holes +and filled-in crevices, ultimately hosing the game completely. + +哪怕碎片不频繁,他还是逐渐把堆变成了有空洞和裂隙的不可用泡沫,最终完全无法运行游戏。 + + + +### The best of both worlds + +### 两个世界中最好的那一个 + +Because of fragmentation and because allocation may be slow, games are very +careful about when and how they manage memory. A simple solution is often best -- +grab a big chunk of memory when the game starts, and don't free it until the game +ends. But this is a pain for systems where we need to create and destroy things +while the game is running. + +由于碎片和分配可能很慢,游戏通常非常小心何时何处管理内存。一个简单的方法是最好的——游戏开始时取一大块内存,然后知道游戏才去释放它。但是这对于我们需要在游戏运行时创建和销毁事物的系统是痛苦的。 + +An object pool gives us the best of both worlds. To the memory manager, we're +just allocating one big hunk of memory up front and not freeing it while the +game is playing. To the users of the pool, we can freely allocate and deallocate +objects to our heart's content. + +一个对象池给了我们两个世界中最好的部分。对于内存管理,我们只需要将一大块内存分出来然后在游戏运行时不释放。对于池的用户,Tom可以简单的构建析构我们心中内容的对象。 + +## The Pattern + +## 模式 + +Define a **pool** class that maintains a collection of **reusable objects**. +Each object supports an **"in use" query** to tell if it is currently "alive". +When the pool is initialized, it creates the entire collection of objects up +front (usually in a single contiguous allocation) and initializes them all to +the "not in use" state. + +定义一个**池**对象包含了一组**可重用对象**。每一个对象支持一个**“在使用中”**来说明他是不是“在使用”。当池被初始化时,它一开始就创建了整个对象集合(通常在一个连续的分配中)然后初始化所有对象到“不在使用中”状态。 + +When you want a new object, ask the pool for one. It finds an available object, +initializes it to "in use", and returns it. When the object is no longer needed, +it is set back to the "not in use" state. This way, objects can be freely +created and destroyed without needing to allocate memory or other resources. + +当你需要一个新对象,向池子要一个。他找到一个可用对象,初始化为“使用中”,然后返回它。当对象不在被需要,他被设置回“不在使用中”。这种方式,对象可以轻易的创建和销毁而不必分配内存或其他资源。 + +## When to Use It + +## 何时使用 + +This pattern is used widely in games for obvious things like game entities and +visual effects, but it is also used for less visible data structures such as currently +playing sounds. Use Object Pool when: + +这个模式因为有些实体和视觉效果被广泛使用,但是它也可以在不那么视觉化的数据结构上使用,比如正在播放的声音,在以下情况中使用对象池: + + * You need to frequently create and destroy objects. + + * 你需要频繁创建和销毁对象。 + + * Objects are similar in size. + + * 对象大小相仿。 + + * Allocating objects on the heap is slow or could lead to memory + fragmentation. + + * 在对上分配对象缓慢或者会导致内存碎片。 + + * Each object encapsulates a resource such as a database or network connection + that is expensive to acquire and could be reused. + + * 每一个对象都封装了像数据库或者网络连接这样很昂贵又可以重用的资源。 + +## Keep in Mind + +## 记住 + +You normally rely on a garbage collector or `new` and `delete` to handle +memory management for you. By using an object pool, you're saying, "I know +better how these bytes should be handled." That means the onus is on you to deal +with this pattern's limitations. + +你通常依赖垃圾回收机制或者`new`和`delete`来为你处理内存管理。通过使用一个对象池,你是在说,“我知道这些字节如何处理更好。”这就意味着处理这个模式的限制的责任在于你了。 + +### The pool may waste memory on unneeded objects + +### 池可能在不需要的对象上浪费内存 + +The size of an object pool needs to be tuned for the game's needs. When tuning, +it's usually obvious when the pool is too *small* (there's nothing like a crash +to get your attention). But also take care that the pool isn't too *big*. A +smaller pool frees up memory that could be used for other fun stuff. + +对象池的大小需要为游戏的需求设置。当当池子太*小*时调整是很明显的(没有什么崩溃来获得你的注意力)。但是小心池子没有太*大*。一个更小的池子缓解了可以做其他有趣事情的内存的压力。 + +### Only a fixed number of objects can be active at any one time + +### 同时只能激活固定数量的对象 + +In some ways, this is a good thing. Partitioning memory into separate pools for +different types of objects ensures that, for example, a huge sequence of +explosions won't cause your particle system to eat *all* of the available +memory, preventing something more critical like a new enemy from being created. + +在某种程度上,这是好事。将内存为不同的对象类型分配分离的池子保证了这一点,举个例子,一连串爆炸不会让你的粒子系统*消耗*所有的可用内存,防止了像创建新的敌人这样的关键事件被阻碍。 + +Nonetheless, this also means being prepared for the possibility that your +attempt to reuse an object from the pool will fail because they are all in use. +There are a few common strategies to handle this: + +尽管如此,这也意味着你试图从池子重用对象可能会失败,因为他们都在使用中。这里有几个通常的策略来处理这个: + + * *Prevent it outright.* This is the most common "fix": tune the pool sizes so + that they never overflow regardless of what the user does. For pools of + important objects like enemies or gameplay items, this is often the right + answer. There may be no "right" way to handle the lack of a free slot to + create the big boss when the player reaches the end of the level, so the + smart thing to do is make sure that never happens. + + * *完全阻止这点。*这是通常的“修复”:增长对象池的大小这样无论用户做什么他们都不会溢出。对于重要对象像敌人或游戏道具,这通常是是正确的选择。这也许没有“正确的”方法来处理在玩家抵达关底的时候创建巨大Boss内存不足问题,所以最聪明的办法就是保证这个不发生。 + + The downside is that this can force you to sit on a lot of memory for object + slots that are needed only for a couple of rare edge cases. Because of this, + a single fixed pool size may not be the best fit for all game states. For + instance, some levels may feature effects prominently while others focus on + sound. In such cases, consider having pool sizes tuned differently for each + scenario. + + 这个的反面是强迫你为那些只在一两个危险边缘需要的对象分配过多的内存。由于这个,一个简单固定的内存池大小也许不对所有的游戏状态都适用。举个例子,某些关卡也许需要更多的效果而其他的需要声音。在这种情况下,思考为每一个场景调整对象池大小。 + + * *Just don't create the object.* This sounds harsh, but it makes sense for + cases like our particle system. If all particles are in use, the screen is + probably full of flashing graphics. The user won't notice if the next + explosion isn't quite as impressive as the ones currently going off. + + * *就不要创建对象了。*这听起来很糟,但是对于像我们粒子系统这样的情况很有道理。如果所有的例子都在使用,那么屏幕已经充满了闪动的图形。用户不会注意到下一个爆炸不像现在运行的这个一样令人印象深刻。 + + * *Forcibly kill an existing object.* Consider a pool for currently playing + sounds, and assume you want to start a new sound but the pool is full. You + do *not* want to simply ignore the new sound -- the user will notice if their + magical wand swishes dramatically *sometimes* and stays stubbornly silent + other times. A better solution is to find the quietest sound already playing + and replace that with our new sound. The new sound will mask the audible + cutoff of the previous sound. + + * *强制干掉一个已有的对象。*思考正在播放声音的内存池,假设你需要播放一条新声音而池子满了。你*不想*简单的忽视新声音——用户会注意到他们的魔法剑有时会发出戏剧般的声音有时顽固的一声不吭。一个更好的解决方法是找到播放中最轻的声音然后用新的声音替代之。新的声音会覆盖掉前一个声音。 + + In general, if the *disappearance* of an existing object would be less + noticeable than the *absence* of a new one, this may be the right choice. + + 大体上,如果已有对象的*消失*要比新对象的*出现*更不引人察觉,这也许是正确的选择。 + + * *Increase the size of the pool.* If your game lets you be a bit more + flexible with memory, you may be able to increase the size of the pool at + runtime or create a second overflow pool. If you do grab more memory in + either of these ways, consider whether or not the pool should contract to + its previous size when the additional capacity is no longer needed. + + * *增加池的大小。*如果你的游戏允许你使用一点内存上的灵活性,我们也许会在运行时增加池子的大小或者创建一个新的溢出池。如果你用这些方式获取内存,考虑池子在增加的存量不再需要的时候是否需要缩回原来的大小。 + +### Memory size for each object is fixed + +### 每个对象的内存大小是固定的 + +Most pool implementations store the objects in an array of in-place objects. If +all of your objects are of the same type, this is fine. However, if you want to +store objects of different types in the pool, or instances of subclasses that +may add fields, you need to ensure that each slot in the pool has enough memory +for the *largest* possible object. Otherwise, an unexpectedly large object will +stomp over the next one and trash memory. + +多数池将对象存储子啊一个数组中。如果你所有的对象都是同样的类型,这很好。但是,如果你想要将不同的对象存储在相同的池中,你需要保证池中的每个位置对*最大的*可能对象都有足够的内存。否则,一个超过预期大小对象会踩到下一个的位置然后破坏内存。 + +At the same time, when your objects vary in size, you waste memory. Each slot +needs to be big enough to accommodate the largest object. If objects are rarely +that big, you're throwing away memory every time you put a smaller one in that +slot. It's like going through airport security and using a huge carry-on-sized +luggage tray just for your keys and wallet. + +同时,如果你的对象是变化的大小,你在浪费内存。每一个草都需要能存储最大的对象。如果对象很少那么大,你每放进去一个小的就是在浪费内存。这很像是通过机场安检时懈怠了一个最大允许尺寸的托盘,里面只放了你的钥匙和钱包。 + +When you find yourself burning a lot of memory this way, consider splitting the +pool into separate pools for different sizes of +object -- big trays for luggage, little trays for pocket stuff. + +当你发现你在用这种方式浪费内存,思考将池为不同大小的对象分割为分离的池子——大的托盘给大行李,小的托盘给口袋里的东西。 + + + +### Reused objects aren't automatically cleared + +### 重用对象不会自动清除。 + +Most memory managers have a debug feature that will clear freshly allocated or +freed memory to some obvious magic value like `0xdeadbeef`. This helps you find +painful bugs caused by uninitialized variables or using memory after it's freed. + +很多内存管理系统有一个查漏特性会清除或释放所有内存成一个特定的值比如`0xdeadbeef`。这帮助你找到由于使用未初始化变量或则使用已被释放内存这样的痛苦漏洞。 + +Since our object pool isn't going through the memory manager any more when it +reuses an object, we lose that safety net. Worse, the memory used for a "new" +object previously held an object of the exact same type. This makes it nearly +impossible to tell if you forgot to initialize something when you created the +new object: the memory where the object is stored may already contain *almost* +correct data from its past life. + +由于你的对象池重用对象不再经过内存管理系统,我们失去了安全网。更糟的是,为“新”对象使用的内存之前存储的是同样类型的相同对象。这当你创建新对象时未初始化的问题就几乎不可能找到了:那个对象存储的内存已经保存了来自于上一个生命周期的*几乎完全*正确的数据。 + +Because of this, pay special care that the code that initializes new objects in +the pool *fully* initializes the object. It may even be worth spending a bit of +time adding a debug feature that clears the memory for +an object slot when the object is reclaimed. + +由于这一点,特别注意在池里初始化对象的代码,保证它*完全*地初始化了对象。这甚至很值得的加一个在对象回收时清空一个对象槽的debug选项。 + + + +### Unused objects will remain in memory + +### 未使用的对象会保留在内存中 + +Object pools are less common in systems that support garbage collection because +the memory manager will usually deal with fragmentation for you. But pools are +still useful there to avoid the cost of allocation and deallocation, especially +on mobile devices with slower CPUs and simpler garbage collectors. + +对象池在支持垃圾回收的系统中很少见,因为内存管理系统通常会为你处理这些碎片。但是池人人是避免构建和析构的有用手段,特别是在有更慢CPU和更简陋垃圾回收系统的移动设备上。 + +If you do use an object pool in concert with a garbage collector, beware of a potential conflict. Since the +pool doesn't actually deallocate objects when they're no longer in use, they +remain in memory. If they contain references to *other* objects, it will prevent +the collector from reclaiming those too. To avoid this, when a pooled object is +no longer in use, clear any references it has to other objects. + +如果你用一个有垃圾回收的对象池系统,注意潜在的冲突。由于池不会真正的在对象不再使用的时候析构他们,如果他们仍然保留任何对*其他*对象的引用,他会也会防止垃圾回收器回收他们。为了避免这一点,但一个池中对象不再使用,清除他对其他对象的所有引用。 + +## Sample Code + +## 示例代码 + +Real-world particle systems will often apply gravity, wind, friction, and other +physical effects. Our much simpler sample will only move particles in a straight +line for a certain number of frames and then kill the particle. Not exactly film +caliber, but it should illustrate how to use an object pool. + +现实世界的粒子系统通常应用重力,风,摩擦,和其他物理效果。我们简陋的例子只在直线上特定帧移动粒子,然后销毁粒子。这不是工业级的品质,但它足够说明如何使用对象池。 + +We'll start with the simplest possible implementation. First up is the little +particle class: + +我们应该从最简单的可能实现开始。第一是小小的粒子类: + +^code 1 + +The default constructor initializes the particle to "not in use". A later call +to `init()` initializes the particle to a live state. Particles are animated +over time using the unsurprisingly named `animate()` function, which should be +called once per frame. + +默认的构造器将粒子初始化为“不在使用中”。之后对`init()`的调用初始化了粒子到活跃状态。粒子随着时间动画使用不出所料的`animate()`函数,它一帧应该被调用一次。 + +The pool needs to know which particles are available for reuse. It gets this +from the particle's `inUse()` function. This function takes advantage of the fact that +particles have a limited lifetime and uses the `framesLeft_` variable to +discover which particles are in use without having to store a separate flag. + +次需要知道哪一个粒子可以重用。它通过粒子的`inUse()`函数获知这一点。这个函数利用了粒子只有有限的生命时间,并使用`framesLeft_`变量来决定哪些粒子在被使用而无需村春一个分离的标识。 + +The pool class is also simple: + +池对象也很简单: + +^code 2 + +The `create()` function lets external code create new particles. The game calls +`animate()` once per frame, which in turn animates +each particle in the pool. + +`create()`函数让其他代码创建新的粒子。游戏每帧调用`animate()`一次,这让池中的粒子轮流显示动画。 + + + +The particles themselves are simply stored in a fixed-size array in the class. +In this sample implementation, the pool size is hardcoded in the class +declaration, but this could be defined externally by using a dynamic array of a +given size or by using a value template parameter. + +粒子本身被简单的存储在类中一个固定大小的数组里。在这个简单的实现中,池的大小在类声明时被硬编码了,但是这也可以使用给定大小的动态数组或使用值模板变量由外部定义。 + +Creating a new particle is straightforward: + +创建一个新的粒子很直观: + +^code 3 + +We iterate through the pool looking for the first available particle. When we +find it, we initialize it and we're done. Note that in this implementation, if +there aren't any available particles, we simply don't create a new one. + +我们遍历池来找到第一个可用的粒子。但我们找到后,他们初始化它然后我们就完成了。注意在这个实现中,如果这里没有任何可用的粒子,我们简单的不创建新的。 + +That's all there is to a simple particle system, aside from rendering the +particles, of course. We can now create a pool and create some particles using +it. The particles will automatically deactivate themselves when their lifetime +has expired. + +做一个简单粒子系统的所有东西都在这里了,当然,没有包含渲染粒子。我们现在可以创建一个池然后使用它创建一些粒子。但他们的时间到了,粒子会自动失效。 + +This is good enough to ship a game, but keen eyes may have noticed that creating +a new particle requires iterating through +(potentially) the entire collection until we find an open slot. If the pool is +very large and mostly full, that can get slow. Let's see how we can improve +that. + +这足够承载一个游戏,但是敏锐的目光也许会注意到创建一个新的粒子需要遍历(可能)整个集合知道我们找到一个空闲槽。如果池子很大很满,这可能很慢。让我们看看我们可以怎样改进这一点。 + + + +### A free list + +### 一个空闲列表 + +If we don't want to waste time *finding* free particles, the obvious answer is +to not lose track of them. We could store a separate list of pointers to each +unused particle. Then, when we need to create a particle, we remove the +first pointer from the list and reuse the particle it points to. + +如果我们不想浪费时间在*查找*空闲粒子上,明星的答案是不要失去对他们的追踪。我们可以存储一个分离的指向每一个未使用的粒子的指针列表。然后,但我们需啊哟创建一个粒子,我们从列表中移除第一个指针,然后重用它指向的粒子。 + +Unfortunately, this would require us to maintain an entire separate array with +as many pointers as there are objects in the pool. After all, when we first +create the pool, *all* particles are unused, so the list would initially have a +pointer to every object in the pool. + +不幸的是,这回要我们管理一个和池中同样大小的分离数组。无论如何,在我们创建这个池的时候,*所有的*粒子都没有用,所以列表初始会对池子中的每一个对象都有一个指针。 + +It would be nice to fix our performance problems *without* sacrificing any +memory. Conveniently, there is some memory already lying around that we can borrow -- +the data for the unused particles themselves. + +如果能够修复我们的性能问题而*无需*牺牲任何内存就好了。方便的是,这里已经有我们可以借走的内存了——那些存储未使用粒子本身的内存。 + +When a particle isn't in use, most of its state is irrelevant. Its position and +velocity aren't being used. The only state it needs is the stuff required to +tell if it's dead. In our example, that's the `framesLeft_` member. All those +other bits can be reused. Here's a revised particle: + +当一个粒子没有被使用,它大部分的状态都是无关紧要的。他的位置和速度没有被使用。唯一它需要的有没有死亡的状态。在我们的例子中,那是`framesLeft_`成员。所有其他的位都可以被重用。这里是一个改进的粒子: + +^code 4 + +We've moved all of the member variables except for `framesLeft_` +into a `live` struct inside a `state_` union. This +struct holds the particle's state when it's being animated. When the particle is +unused, the other case of the union, the `next` member, is used. It holds a +pointer to the next available particle after this one. + +我们将除`framesLeft_`外的所有成员变量移到一个union中的`state_`中的`live`结构。这个结构在它被动作的时候保存粒子的状态。当粒子被重用时,union的其他部分,`next`成员被使用了。她保持了一个指向这一个后面的其他可用粒子。 + + + +We can use these pointers to build a linked list that chains together every +unused particle in the pool. We have the list of available particles we need, +but we didn't need to use any additional memory. Instead, we cannibalize the memory +of the dead particles themselves to store the list. + +我们可以使用这些指针构建一个链表,将每一个池中的未使用粒子都连在一起。我们有每个可用的粒子,但是我们无需使用多余的内存。相反,我们调用了死亡粒子本身的内存来排列列表。 + +This clever technique is called a [*free +list*](http://en.wikipedia.org/wiki/Free_list). For it to work, we need to make +sure the pointers are initialized correctly and are maintained when particles +are created and destroyed. And, of course, we need to keep track of the list's +head: + +这种聪明的技术被称为[*freelist*](http://en.wikipedia.org/wiki/Free_list)。为了让其工作,我们需要保证指针正确的初始化,在粒子创建和销毁时好好被管理了。并且,当然,我们需哟追踪列表的头: + +^code 5 + +When a pool is first created, *all* of the particles are available, so our free +list should thread through the entire pool. The pool constructor sets that up: + +当一个池被首次创建,*所有的*粒子都是可用的,所以我们的空余列表应该穿过整个池。池构造器设置了这些: + +^code 6 + +Now to create a new particle, we jump directly to the first available one: + +现在为了创建一个新的粒子,我们直接跳到第一个可用的: + + + +^code 7 + +We need to know when a particle dies so we can add it back to the free list, so +we'll change `animate()` to return `true` if the previously live particle gave +up the ghost in that frame: + +我们需要知道当粒子死亡这样我们可以将其放回到空闲列表中,所以我们将`animate()`改为在上一个活跃粒子放弃了它这一帧的影子时返回`true`: + +^code particle-animate + +When that happens, we simply thread it back onto the list: + +当这个发生时,我们简单的将其放回列表: + +^code 8 + +There you go, a nice little object pool with constant-time creation and +deletion. + +这样就成了,一个小的对象池,拥有常量时间的构造和删除。 + +## Design Decisions + +## 设计决策 + +As you've seen, the simplest object pool implementation is almost trivial: +create an array of objects and reinitialize them as needed. Production code is +rarely that minimal. There are several ways to expand on that to make the pool +more generic, safer to use, or easier to maintain. As you implement pools in +your games, you'll need to answer these questions: + +就像你看到的,最简单的对象池实现是几乎无关紧要的:创建一个对象数组然后在需要他们的时候重新初始化。产出的代码很少会那么少,这里还有很多方式让池更加的通用,安全,或容易管理。在你在游戏中实现对象池的时候,你需要回答以下问题: + +### Are objects coupled to the pool? + +### 对象和池耦合吗? + +The first question you'll run into when writing an object pool is whether the +objects themselves know they are in a pool. Most of the time they will, but you +won't have that luxury when writing a generic pool class that can hold arbitrary +objects. + +第一个你写对象池需要思考的问题就是是否对象本身需要知道他们在一个池子中。大多数情况下他们需要,但是你不大可能写一个通用对象池类来保持任意对象。 + + * **If objects are coupled to the pool:** + + * **如果对象与池耦合:** + + * *The implementation is simpler.* You can simply put an "in use" flag or + function in your pooled object and be done with it. + + * *实现更简单。*你可以简单的在对象中放一个“在使用中”标识或者函数然后就完成了。 + + * *You can ensure that the objects can only be created by the pool.* In + C++, a simple way to do this is to make the pool class a friend of the + object class and then make the object's constructor private. + + * *你可以保证对象只能被池创建。*在C++中,做这事最简单的方法是让池对象是对象类的友类,让对象的构造器私有。 + + ^code 10 + + This relationship documents the intended way to use the class and + ensures your users don't create objects that aren't tracked by the pool. + + 在类间保持这种关系来确保你的用户无法创建对象池没有追踪的对象。 + + * *You may be able to avoid storing an explicit "in use" flag.* Many + objects already retain some state that could be used to tell whether it + is alive or not. For example, a particle may be available for reuse if + its current position is offscreen. If the object class knows it may be + used in a pool, it can provide an `inUse()` method to query that state. + This saves the pool from having to burn some extra memory storing a + bunch of "in use" flags. + + 8 *你也许可以避免存储显示的“在使用中”标识。*很多对象已经保存了一些状态可以告诉外界它有没有在使用。举个例子,一个粒子的位置如果不在屏幕上,也许他就可以被重用。如果对象类知道它会在对象池中,那他可以提供一个`inUse()`来查询那个状态。这省下了对象池存储一堆“在使用中”的标识的多余内存。 + + * **If objects are not coupled to the pool:** + + * **如果对象没有和池子耦合:** + + * *Objects of any type can be pooled.* This is the big advantage. By + decoupling objects from the pool, you may be able to implement a generic + reusable pool class. + + * *多种类型的对象可以被保存。*这是最大的好处。通过解耦对象和对象池,你可以实现一种通用的可重用对象池类。 + + * *The "in use" state must be tracked outside the objects.* The simplest + way to do this is by creating a separate bit field: + + * *“使用中”的状态必须在对象的外部追踪。*做这点最简单的方式是创建一个分离的位字段。 + + ^code 11 + +### What is responsible for initializing the reused objects? + +### 谁负责初始化重用对象? + +In order to reuse an existing object, it must be reinitialized with new state. A +key question here is whether to reinitialize the object inside the pool class or +outside. + +为了重用一个已经存在的对象,它必须用新状态重新初始化。这里的一个关键问题是你需要在池类的内部还是外部重新初始化。 + + * **If the pool reinitializes internally:** + + * **如果池内部重新初始化:** + + * *The pool can completely encapsulate its objects*. Depending on the + other capabilities your objects need, you may be able to keep them + completely internal to the pool. This makes sure that other code doesn't + maintain references to objects that could be unexpectedly reused. + + * *池子可以完全封装它的对象。*取决于你对象需啊哟的其他能力,你可能可以让他们完全在池子的内部。这保证了其他代码不会有不小心重用了的对象的引用。 + + * *The pool is tied to how objects are initialized*. A pooled object may + offer multiple functions that initialize it. If the pool manages + initialization, its interface needs to support all of those and forward + them to the object. + + * *池子绑定到了对象是如何初始化的。*一个池中对象也许提供不同的函数来初始化。如果吃控制了初始化,它的借口需要支持所有的,然后转送给对象。 + + ^code 12 + + * **If outside code initializes the object:** + + * **如果外部代码初始化对象:** + + * *The pool's interface can be simpler.* Instead of offering multiple + functions to cover each way an object can be initialized, the pool can + simply return a reference to the new object: + + * *对象借口更简单。*不是提供多种可以覆盖每一种对象初始化的函数,池只需要返回一个新对象的引用: + + ^code 13 + + The caller can then initialize the object by calling any method the + object exposes: + + 滴啊用这可以在暴露的对象上使用任何可能的方法初始化之。 + + ^code 14 + + * *Outside code may need to handle the failure to create a new object.* + The previous example assumes that `create()` will always successfully + return a pointer to an object. If the pool is full, though, it may + return `NULL` instead. To be safe, you'll need to check for that before + you try to initialize the object: + + * *外部代码需要处理无法创建新对象的失败。*前面的粒子假设`create()`总能成功的返回一个指向对象的指针。但如果池是满的,它会返回`NULL`。为了安全,你需啊哟在初始化之前检查这一点。 + + ^code 15 + +## See Also + +## 参见 + + * This looks a lot like the + Flyweight pattern. Both maintain a collection of reusable objects. The + difference is what "reuse" means. Flyweight objects are reused by sharing + the same instance between multiple owners *simultaneously*. The Flyweight pattern avoids + *duplicate* memory usage by using the same object in multiple contexts. + + 这看上去很像是享元模式。两者都控制了一系列可重要对象。不同在于“重用”的意思。享元对象通过分享实例间*同时*拥有的相同部分。享元模式通过在不同上下文使用相同对象避免了*复制*内存使用。 + + The objects in a pool get reused too, but only over time. "Reuse" in the + context of an object pool means reclaiming the memory for an object *after* + the original owner is done with it. With an object pool, there isn't any + expectation that an object will be shared within its lifetime. + + 池中的对象也被重用了,但在不同的时间上。“重用”在对象池中意味着为一个对象在原先的主人用完*之后*分配内存。通过对象池,这里没有期待对象会在他的生命周期中分享什么。 + + * Packing a bunch of objects of the same type together in memory helps keep + your CPU cache full as the game iterates over those objects. The Data Locality pattern is all + about that. + + * 选择内存中同样类型的对象帮助你的CPU缓存在游戏遍历这些对象时总是满的。数据局部性模式全部关于这一点。 diff --git a/book/observer.markdown b/book/observer.markdown new file mode 100644 index 0000000..c882e62 --- /dev/null +++ b/book/observer.markdown @@ -0,0 +1,945 @@ +^title Observer +^section Design Patterns Revisited + +You can't throw a rock at a computer without hitting an application built using +the [Model-View-Controller][MVC] architecture, and +underlying that is the Observer pattern. Observer is so pervasive that Java put +it in its core library ([`java.util.Observer`][java]) and C# baked it right into +the *language* (the [`event`][event] keyword). + +你扔一块石头到电脑中,不可能砸不中一个不使用MVC架构的应用,根本在于观察者模式。观察者模式是如此的普遍,Java将其放到了核心库之中,而C#直接将其嵌入了*语言*。 + +[MVC]: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller +[java]: http://docs.oracle.com/javase/7/docs/api/java/util/Observer.html +[event]: http://msdn.microsoft.com/en-us/library/8627sbea.aspx + + + +Observer is one of the most widely used and widely known of the original Gang of +Four patterns, but the game development world can be strangely cloistered at +times, so maybe this is all news to you. In case you haven't left the abbey in a +while, let me walk you through a motivating example. + +观察者模式是最广泛使用和最广为人知的GOF模式,但是游戏开发世界与世隔绝,所以这个也许对你是全新的领域。防止你还没有从修道院中走出来,让我带你走到一个更加形象的例子。 + +## Achievement Unlocked + +## 成就解锁 + +Say we're adding an achievements system to our game. +It will feature dozens of different badges players can earn for completing +specific milestones like "Kill 100 Monkey Demons", "Fall off a Bridge", or +"Complete a Level Wielding Only a Dead Weasel". + +假设我们向游戏中添加了一个成就系统。它存储了玩家可以完成的各种各样的成就,比如“杀死1000只猴子恶魔”,“从桥上掉下去”,或者“一命通关”。 + + + +This is tricky to implement cleanly since we have such a wide range of +achievements that are unlocked by all sorts of different behaviors. If we aren't +careful, tendrils of our achievement system will twine their way through every +dark corner of our codebase. Sure, "Fall off a Bridge" is somehow tied to the +physics engine, but do we really want to see a call +to `unlockFallOffBridge()` right in the middle of the linear algebra in our +collision resolution algorithm? + +有如此之多的行为可以完成的如此宽泛的成就系统,清晰的实现它是很有技巧的。如果我们不够小心,成就系统会缠绕在我们代码库的每一个黑暗角落。当然,“从桥上掉落”被绑定到物理引擎上,在处理撞击代码的线性代数时有一个对`unlockFallOffBridge()`的调用不是我们想看到的,对吧? + + + +What we'd like, as always, is to have all the code concerned with one facet of +the game nicely lumped in one place. The challenge is that achievements are +triggered by a bunch of different aspects of gameplay. How can that work without +coupling the achievement code to all of them? + +我们喜欢的是,总是,让所有的代码都关注游戏代码中集成的一块。挑战是成就在游戏的不同层面被触发。我们怎么能解耦成就系统和他们呢? + +That's what the observer pattern is for. It lets one piece of code announce that +something interesting happened *without actually caring who receives the +notification*. + +这就是观察者模式出现的原因。这让代码的一块宣称有些有趣的事情发生了*而不必指定谁接受通知。* + +For example, we've got some physics code that handles gravity and tracks which +bodies are relaxing on nice flat surfaces and which are plummeting toward sure +demise. To implement the "Fall off a Bridge" badge, we could just jam the +achievement code right in there, but that's a mess. Instead, we can just do: + +举个例子,我们有一些物理代码处理重力,追踪那些物体轻松待在地表,哪些坠入深渊。为了实现“桥上掉落”的徽章,我们可以直接把成就代码放在那里,但这就会一团糟。相反,我们可以这样做: + +^code physics-update + +All it does is say, "Uh, I don't know if anyone +cares, but this thing just fell. Do with that as you will." + +所有他说的就是,“额,我不知道谁感兴趣,但是有东西刚刚掉下去了。做你想做的事吧。” + + + +The achievement system registers itself so that whenever the physics code sends +a notification, the achievement system receives it. It can then check to see if +the falling body is our less-than-graceful hero, and if his perch prior to this +new, unpleasant encounter with classical mechanics was a bridge. If so, it +unlocks the proper achievement with associated fireworks and fanfare, and it +does all of this with no involvement from the physics code. + +成就系统注册它自己,这样无论何时物理代码发送一个通知,成就系统就收到。它可以然后检查掉落的物体是不是我们不那么伟大的英雄,他之前有没有做过这种新的,不愉快的与桥的经典力学遭遇。如果这样,它解锁了合适的成就,伴着礼花和炫光,它做完所有这些而不必牵扯到物理代码。 + +In fact, we can change the set of achievements or tear +out the entire achievement system without touching a line of the physics engine. +It will still send out its notifications, oblivious to the fact that nothing is +receiving them anymore. + +事实上,我们可以改变成就的集合或者删除整个成就系统而不必修改一行物理引擎。它仍然会发送它的通知,哪怕事实上没有东西在接收。 + + + +## How it Works + +## 它如何运作 + +If you don't already know how to implement the pattern, you could probably +guess from the previous description, but to keep things easy on you, I'll walk +through it quickly. + +如果你还不知道如何实现这个模式,你可能可以从之前的描述中猜到,但是减轻你的负担,我会过一遍代码。 + +### The observer + +### 观察者 + +We'll start with the nosy class that wants to know when another object does +something interesting. These inquisitive objects are defined by this interface: + +我们从那个需要知道别的对象做了什么有趣事情的吵闹类开始。这些好打听的对象用如下接口定义: + + + +^code observer + + + +Any concrete class that implements this becomes an observer. In our example, +that's the achievement system, so we'd have something like so: + +任何实现了这个的具体类成为了一个观察者。在我们的例子中,那是成就系统,所以我们会做些像这样的事。 + +^code achievement-observer + +### The subject + +### 客体 + +The notification method is invoked by the object being observed. In Gang of Four +parlance, that object is called the "subject". It has two jobs. First, it holds +the list of observers that are waiting oh-so-patiently for a missive from it: + +接受观察的对象拥有通知方法,以GoF的说法那些对象被称为“客体”。它有两个工作。首先,它保持默默等待它的观察者列表: + + + +^code subject-list + + + +The important bit is that the subject exposes a *public* API for modifying that +list: + +重点是客体暴露了一个*公开*API来修改这个列表: + +^code subject-register + +That allows outside code to control who receives notifications. The subject +communicates with the observers, but it isn't *coupled* to them. In our example, +no line of physics code will mention achievements. Yet, it can still talk to the +achievements system. That's the clever part about this pattern. + +这就允许了外界代码控制谁接收通知。客体与观察者交流,但是不与他们*耦合*。在我们的例子中,没有一行物理代码会提及成就。现在,它仍然可以与成就系统交流。这就是这个模式的聪慧之处。 + +It's also important that the subject has a *list* of observers instead of a +single one. It makes sure that observers aren't implicitly coupled to *each +other*. For example, say the audio engine also observes the fall event so that +it can play an appropriate sound. If the subject only supported one observer, +when the audio engine registered itself, that would *un*-register the +achievements system. + +客体有一*列表*观察者而不是一个也是很重要的。这保证了观察者不会相互竞争。举个例子,假设我们的音频引擎也需要观察坠落事件来播放合适的音乐。如果客体只支持一个观察者,当音频引擎注册了它自己,这就会*取消*成就系统的注册。 + +That means those two systems would interfere with each other -- and in a +particularly nasty way, since the second would disable the first. Supporting a +list of observers ensures that each observer is treated independently from the +others. As far as they know, each is the only thing in the world with eyes on +the subject. + +这意味着这两个系统需要相互交互——使用一种极其糟糕的方式,由于第二个会使第一个失效。支持一列表的观察者保证了每一个观察者都是与其他独立处理的。他们知道的所有事情是,他是世界上唯一看着客体的东西。 + +The other job of the subject is sending notifications: + +客体的剩余任务就是发送通知: + + + +^code subject-notify + + + +### Observable physics + +### 可观察物理 + +Now, we just need to hook all of this into the physics engine so that it can send +notifications and the achievement system can wire itself up to receive them. +We'll stay close to the original *Design Patterns* recipe and inherit `Subject`: + +现在,我们只需要给物理引擎绑上钩子,这样他可以发送消息,成就系统可以将它自己接上线来接受他们。我们靠近之前的*设计模式*秘方,然后继承`Subject`: + +^code physics-inherit + +This lets us make `notify()` in `Subject` protected. That way the derived +physics engine class can call it to send notifications, but code outside of it +cannot. Meanwhile, `addObserver()` and `removeObserver()` are public, so +anything that can get to the physics system can observe it. + +这让我们在`Subject`中保护的完成`notify()`。这样推导的物理引擎类可以调用发送通知,但是外部的代码不行。同时,`addObserver()`和`removeObserver()`是公开的,所以任何可以接触物理引擎的东西都可以观察它。 + + + +Now, when the physics engine does something noteworthy, it calls `notify()` +like in the motivating example before. That walks the observer list and gives +them all the heads up. + +现在,当物理引擎做了些值得关注的东西,它调用`notify()`,就像之前的刺激例子。它遍历了观察者列表然后通知所有。 + +A Subject containing a list of Observer pointers. The first two point to Achievements and Audio. + +Pretty simple, right? Just one class that maintains a list of pointers to +instances of some interface. It's hard to believe that something so +straightforward is the communication backbone of countless programs and app +frameworks. + +很简单,对吧?只要一个类管理一列表指针指向一些接口的实例。很难相信如此直观的东西是无数程序和应用框架交流的主心骨。 + +But the Observer pattern isn't without its detractors. When I've asked other +game programmers what +they think about this pattern, they bring up a few complaints. Let's see what we +can do to address them, if anything. + +观察者模式不是没有诋毁者。但我问其他程序员他们怎么看,他们提出了一些抱怨。让我们看看我们可以做些什么来掌控,如果有事能做的话。 + +## "It's Too Slow" + +## 太慢了 + +I hear this a lot, often from programmers who don't actually know the details of +the pattern. They have a default assumption that anything that smells like a +"design pattern" must involve piles of classes and indirection and other +creative ways of squandering CPU cycles. + +我经常听到这一点,通常是从那些不知道模式的细节的程序员那里。他们有一种假设,只要有东西听起来像是“设计模式”一定包含了一堆类,跳转和其他创造性的方式浪费CPU循环。 + +The Observer pattern gets a particularly bad rap here because it's been known to +hang around with some shady characters named "events", "messages", and even "data binding". Some of those systems +*can* be slow (often deliberately, and for good reason). They involve things +like queuing or doing dynamic allocation for each notification. + +观察者模式有一个特别坏的名声,因为他通常与一些坏名声的事物结伴出行,比如“事件”,“消息”,甚至“数据绑定”。其中的一些系统*会*慢。(通常是故意的,有一个很好的原因)。他们包含了队列或者为每一个通知做些动态分配。 + + + +But, now that you've seen how the pattern is actually implemented, you know that +isn't the case. Sending a notification is simply walking a list and calling some +virtual methods. Granted, it's a *bit* slower than a statically dispatched +call, but that cost is negligible in all but the most performance-critical +code. + +现在你看到了模式是如何真正被实现的,你知道了这并不是这样。发送一个通知只是简单的遍历列表然后调用一些虚方法。是的,这回避静态调用慢*一点*,但是这消耗在大多数性能攸关的代码都是微不足道的。 + +I find this pattern fits best outside of hot code paths anyway, so you can +usually afford the dynamic dispatch. Aside from that, there's virtually no +overhead. We aren't allocating objects for messages. There's no queueing. It's +just an indirection over a synchronous method call. + +我发现这个模式在热点代码路径之外有很好的应用,所以你可以付得起动态分配的消耗。除了那点,这里几乎没有天花板。我们不必为消息分配对象。这里没有队列。这里只有一个同步方法调用的跳转。 + +### It's too *fast?* + +### 太*快*? + +In fact, you have to be careful because the Observer pattern *is* synchronous. +The subject invokes its observers directly, which means it doesn't resume its +own work until all of the observers have returned from their notification +methods. A slow observer can block a subject. + +事实上,你得小心观察者模式*是*同步的。客体直接引入了他的观察者,这就意味着直到所有他的观察者从他们的通知方法返回后才会继续它自己的工作。一个缓慢的观察者会阻塞客体。 + +This sounds scary, but in practice, it's not the end of the world. It's just +something you have to be aware of. UI programmers -- who've been doing +event-based programming like this for ages -- have a time-worn motto for this: +"stay off the UI thread". + +这听起来很疯狂,但在实践中,这还不是世界末日。这只是你得注意的事情。UI程序员——那些基于事件的编程已经这么干了好几年了——有句经典名言:“滚出UI线程”。 + +If you're responding to an event synchronously, you need to finish and return +control as quickly as possible so that the UI doesn't lock up. When you have +slow work to do, push it onto another thread or a work queue. + +如果年级对事件同步响应,你需要完成然后尽可能的返回控制权,这样UI不会锁死。当你有缓慢工作要做的时候,将其推到另一个线程或工作队列中去。 + +You do have to be careful mixing observers with threading and explicit locks, +though. If an observer tries to grab a lock that the subject has, you can +deadlock the game. In a highly threaded engine, you may be better off with +asynchronous communication using an Event Queue. + +你需要小心观察者混合线程和锁。如果一个观察者试图获得一个客体有的锁,你就死锁了游戏。在更高线程的机器,你最好使用事件队列来做异步通信。 + +## "It Does Too Much Dynamic Allocation" + +## “它做了太多动态分配” + +Whole tribes of the programmer clan -- including many game developers -- have +moved onto garbage collected languages, and dynamic allocation isn't the boogie +man that it used to be. But for performance-critical software like games, memory +allocation still matters, even in managed languages. Dynamic allocation takes time, as does reclaiming memory, +even if it happens automatically. + +整个程序员部落——包括很多游戏开发者——移到了垃圾回收语言,动态分配不再是以前的样子了。的那会对于性能攸关的软件比如游戏,内存分配仍然重要,哪怕是在有管理的语言。动态分配需要时间,回收内存也需要时间,哪怕是自动运行的。 + + + +In the example code before, I used a fixed array because I'm trying to keep +things dead simple. In real implementations, the observer list is almost always +a dynamically allocated collection that grows and shrinks as observers are +added and removed. That memory churn spooks some people. + +在上面的示例代码中,我使用了一个固定数组因为我想尽可能保证简单。在真实的实现中,观察者列表总是在动态的随着观察者的添加和删除而增长和消减。这种内存搅拌吓坏了一些人。 + +Of course, the first thing to notice is that it only allocates memory when +observers are being wired up. *Sending* a notification requires no memory +allocation whatsoever -- it's just a method call. If you hook up your observers +at the start of the game and don't mess with them much, the amount of allocation +is minimal. + +当然,第一件需要注意的事情是只在观察者连线的时候分配内存。*发送*一个通知不需要内存分配——只是一个方法调用。如果你在游戏一开始就挂上了你的观察者而不乱搞它们,分配的总量是很小的。 + +If it's still a problem, though, I'll walk through a way to implement adding and +removing observers without any dynamic allocation at all. + +但是,如果这还是问题,我会介绍一种方式实现增加和删除观察者无需任何动态分配。 + +### Linked observers + +### 连接观察者 + +In the code we've seen so far, `Subject` owns a list of pointers to each +`Observer` watching it. The `Observer` class itself has no reference to this +list. It's just a pure virtual interface. Interfaces are preferred over +concrete, stateful classes, so that's generally a good thing. + +我们现在看到的所有代码中,`Subject`拥有一列指针指向观察它的`Observer`。`Observer`类本身没有对这个列表的引用。他只是一个纯虚接口。接口比具体有状态的类更加受欢迎,所以这大体上是一件好事。 + +But if we *are* willing to put a bit of state in `Observer`, we can solve our +allocation problem by threading the subject's list *through the observers +themselves*. Instead of the subject having a separate collection of pointers, +the observer objects become nodes in a linked list: + +但是如果我们*确实*愿意在`Observer`中放一些状态,我们可以通过将客体的列表包含*观察者自己*来解决我们的分配问题。不是客体有一集合分散的指针,观察者对象成为了链表中的一部分: + +A linked list of Observers. Each has a next_ field pointing to the next one. A Subject has a head_ pointing to the first Observer. + +To implement this, first we'll get rid of the array in `Subject` and replace it +with a pointer to the head of the list of observers: + +为了实现这一点,我们首先要摆脱`Subject`中的数组然后用链表头部的指针取而代之。 + +^code linked-subject + +Then we'll extend `Observer` with a pointer to the next observer in the list: + +然后,我们在`Observer`中添加指向列表中下一个观察者的指针。 + +^code linked-observer + +We're also making `Subject` a friend class here. The subject owns the API for adding +and removing observers, but the list it will be managing is now inside the +`Observer` class itself. The simplest way to give it the ability to poke at that +list is by making it a friend. + +我们也可以让`Subject`成为一个友类。客体拥有增加和删除观察者的API,但是列表现在在`Observer`内部管理。获得接触这个列表最简单的办法就是让他成为友类。 + +Registering a new observer is just wiring it into the list. We'll take the easy +option and insert it at the front: + +注册一个新的观察者就是将其连到链表中。我们使用更简单的选项,将其插到前头。 + +^code linked-add + +The other option is to add it to the end of the linked list. Doing that adds a +bit more complexity. `Subject` has to either walk the list to find the end or +keep a separate `tail_` pointer that always points to the last node. + +另一个选项是将其添加到链表的末尾。这么做增加了一定的复杂性。`Subject`需要遍历整个链表来找到尾部或者保留一个分离的`tail_`指针指向最后一个节点。 + +Adding it to the front of the list is simpler, but does have one side effect. +When we walk the list to send a notification to every observer, the most +*recently* registered observer gets notified *first*. So if you register +observers A, B, and C, in that order, they will receive notifications in C, B, A +order. + +将它增加在列表的头很简单,但也有另一个副作用。但我们遍历列表给每一个观察者发送一个通知,最*近*注册的观察者最*先*接到通知。所以如果以A,B,C的顺序来注册观察者,他们会以C,B,A的顺序接到通知。 + +In theory, this doesn't matter one way or the other. It's a tenet of good +observer discipline that two observers observing the same subject should have no +ordering dependencies relative to each other. If the ordering *does* matter, it +means those two observers have some subtle coupling that could end up biting +you. + +理论上,这种方式和那种没什么区别。一种好的观察者设计原则是观察统一客体的两个观察者互相不应该有任何顺序相关。如果顺序*确实*有影响,这意味着这两个观察者有一些微妙的耦合最终会伤到你。 + +Let's get removal working: + +让我们把删除完成: + + + +^code linked-remove + + + +Because we have a singly linked list, we have to walk it to find the observer +we're removing. We'd have to do the same thing if we were using a regular array +for that matter. If we use a *doubly* linked list, where each observer has a +pointer to both the observer after it and before it, we can remove an observer +in constant time. If this were real code, I'd do that. + +因为我们有一个链表,得遍历它找到我们删除的观察者。如果我们使用一个普通的数组也要做相同的事。如果我们使用*双向*链表,每一个观察者都有指向前面和后面的各一个指针,我们可以用常量时间移除一个观察者。如果这是真实代码,我会那么做。 + +The only thing left to do is send a notification. +That's as simple as walking the list: + +唯一需要做的事情是发送一个通知,这只需要简单的遍历列表; + +^code linked-notify + + + +Not too bad, right? A subject can have as many observers as it wants, without a +single whiff of dynamic memory. Registering and unregistering is as fast as it +was with a simple array. We have sacrificed one small feature, though. + +不差嘛,对吧?一个客体现在想有多少观察者就有多少观察者,不必使用动态内存。注册和取消注册就像一个简单数组一样快。但是,我们牺牲了一些小小的特性。 + +Since we are using the observer object itself as a list node, that implies it +can only be part of one subject's observer list. In other words, an observer can +only observe a single subject at a time. In a more traditional implementation +where each subject has its own independent list, an observer can be in more than +one of them simultaneously. + +由于我们使用观察者对象作为链表节点,这暗示了它只能在一个对象的观察者链表中。换言之,一个观察者一次只能观察一个客体。在传统的实现中,每一个客体有他独立的列表,一个观察者同时可以在多个列表中。 + +You may be able to live with that limitation. I find it more common for a +*subject* to have multiple *observers* than vice versa. If it *is* a problem for +you, there is another more complex solution you can use that still doesn't +require dynamic allocation. It's too long to cram into this chapter, but I'll +sketch it out and let you fill in the blanks... + +你也许可以接受这一限制。我发现一个*客体*有多个*观察者*要比相反更常见。如果这*是*一个问题,这里还有一种不必使用动态分配的解决方案。要将这个塞到这章就太长了,但我会大致描述,让你填补空白。 + +### A pool of list nodes + +### 一池链表节点 + +Like before, each subject will have a linked list of observers. However, those +list nodes won't be the observer objects themselves. Instead, they'll be +separate little "list node" objects that contain a +pointer to the observer and then a pointer to the next node in the list. + +就像以前,每一个客体有一个观察者的链表。但是,这些链表节点不是观察者本身。相反,他们是分散的小“链表节点”对象包含了一个指向观察者的指针和另一个指向链表下一节点的指针。 + +A linked list of nodes. Each node has an observer_ field pointing to an Observer, and a next_ field pointing to the next node in the list. A Subject's head_ field points to the first node. + +Since multiple nodes can all point to the same observer, that means an observer +can be in more than one subject's list at the same time. We're back to being +able to observe multiple subjects simultaneously. + +由于多个节点可以指向同一个观察者,这就意味着一个观察者可以同时在超过一个客体列中。我们同时可以观察多个对象了。 + + + +The way you avoid dynamic allocation is simple: since all of those nodes are the +same size and type, you pre-allocate an Object Pool of them. That gives you a fixed-size pile of +list nodes to work with, and you can use and reuse them as you need without +having to hit an actual memory allocator. + +你避免动态分配的方法很简单:由于这些节点都是同样大小和类型,你可以预先在一个对象池中分配他们。这给了你一个固定大小的列表节点工作,你可以随你所需使用并重用他们,无需使用一个真正的内存分配器。 + +## Remaining Problems + +## 剩余的问题 + +I think we've banished the three boogie men used to scare people off this +pattern. As we've seen, it's simple, fast, and can be made to play nice with +memory management. But does that mean you should use observers all the time? + +我认为我们已经搞定了三个主要将人们吓阻这个模式的问题。我们看到,它简单,快速,对内存管理友好。但是这意味着你总是应该使用观察者吗? + +Now, that's a different question. Like all design patterns, the Observer pattern +isn't a cure-all. Even when implemented correctly and efficiently, it may not be +the right solution. The reason design patterns get a bad rap is because people +apply good patterns to the wrong problem and end up making things worse. + +现在,这是一个不同的问题。就像所有的设计模式,观察者模式不是万能药。哪怕可以实现的正确高效,它也不一定是好的解决方案。设计模式获得一个坏名声的原因就是人们将好的模式运用在错误的问题上,最后结果更糟。 + +Two challenges remain, one technical and one at something more like the +maintainability level. We'll do the technical one first because those are always +easiest. + +还剩两个挑战,一个是技术性的,另一个更像是可维护性层次。我们先处理技术性的,因为这些总是更好处理。 + +### Destroying subjects and observers + +### 销毁客体和观察者 + +The sample code we walked through is solid, but it +side-steps an important issue: what happens when +you delete a subject or an +observer? If you carelessly call `delete` on some observer, a subject may still +have a pointer to it. That's now a dangling pointer into deallocated memory. +When that subject tries to send a notification, well... let's just say you're +not going to have a good time. + +我们看到的样例代码很坚固,但是它有一个严重的副作用:当你删除一个客体或观察者时发生了什么?如果你不小心的在某些观察者上面调用了`delete`,一块客体也许仍然持有指向它的指针。这就是指向一片释放了悬挂指针。但可以试图发送一个通知,好吧……就说你不会有好时光吧。 + + + +Destroying the subject is easier since in most implementations, the observer +doesn't have any references to it. But even then, sending the subject's bits to +the memory manager's recycle bin may cause some problems. Those observers may +still be expecting to receive notifications in the future, and they don't know +that that will never happen now. They aren't observers at all, really, they just +think they are. + +删除客体更加容易,因为在大多数实现中,观察者没有对它的引用。但是即使这样,客体的位到内存管理系统 的回收站也许会造成一些问题。这些观察者也许仍然期待在以后收到通知,而他们不知道这再也不会发生了。他们不再是观察者了,真的,他们只是认为他们是。 + +You can deal with this in a couple of different ways. The simplest is to do what +I did and just punt on it. It's an observer's job to unregister itself from any +subjects when it gets deleted. More often than not, the observer *does* know +which subjects it's observing, so it's usually just a matter of adding a `removeObserver()` call to its destructor. + +你可以用好几种方式处理这点。最简单的是就像我做的然后一脚踩空。这是观察者的职责在其被删除的时候取消注册。多数情况下,观察者*确实*知道它在观察哪个实体,所以这通常是添加一个`removeObserver()`调用给它的析构器的工作量。 + + + +If you don't want to leave observers hanging when a subject gives up the ghost, +that's easy to fix. Just have the subject send one final "dying breath" +notification right before it gets destroyed. That way, any observer can receive +that and take whatever action it thinks is +appropriate. + +如果在客厅放弃存在,不想让观察者挂着,这也很好解决。只需要让客体在它被摧毁前发送一个最终的“死亡叹息”通知。通过这种方式,任何观察者都可以接收到然后做那些他认为合适的行为。 + + + +People -- even those of us who've spent enough time in the company of machines +to have some of their precise nature rub off on us -- are reliably terrible at +being reliable. That's why we invented computers: they don't make the mistakes +we so often do. + +人们——哪怕是那些花费在大量时间在机器前,拥有让我们黯然失色才能的人——也是可靠地不可靠。这就是为什么我们发明了电脑:他们不像我们那样经常犯错误。 + +A safer answer is to make observers automatically unregister themselves from +every subject when they get destroyed. If you implement the logic for that once +in your base observer class, everyone using it doesn't have to remember to do it +themselves. This does add some complexity, though. It means each *observer* will +need a list of the *subjects* it's observing. You end up with pointers going in +both directions. + +更安全的回答是在每一个客体销毁时,让观察者自动取消注册。若干你在一个观察者基类中实现了这个逻辑,每个人不必记住就可以使用它。这确实增加了一定的复杂度。这意味着每个*观察者*都需要一个它在观察的*客体*列表。你最终拥有独立双向指针。 + +### Don't worry, I've got a GC + +### 别担心,我有垃圾回收器 + +All you cool kids with your hip modern languages with garbage collectors are +feeling pretty smug right now. Think you don't have to worry about this because +you never explicitly delete anything? Think again! + +你们那些装备有垃圾回收系统的孩子现在一定很洋洋自得。觉得你不必担心这个因为你从来不必显式删除任何东西?再想一想! + +Imagine this: you've got some UI screen that shows a bunch of stats about the +player's character like their health and stuff. When the player brings up the +screen, you instantiate a new object for it. When they close it, you just forget +about the object and let the GC clean it up. + +想象一下这个:你有UI显式一些了玩家角色情况的状态比如健康和道具。当玩家到屏幕上时,你为其初始化了一个对象。当他们退出时,你直接忘掉那个对象让GC清理。 + +Every time the character takes a punch to the face (or elsewhere, I suppose), it +sends a notification. The UI screen observes that and updates the little health +bar. Great. Now what happens when the player dismisses the screen, but you don't +unregister the observer? + +每一个角色脸上(或者其他什么地方)挨了一拳,它发送一个通知。UI屏幕观察到了按个然后更新健康槽。很好。当玩家离开了屏幕,但你没有取消观察者的注册会发生什么? + +The UI isn't visible anymore, but it won't get garbage collected since the +character's observer list still has a reference to it. Every time the screen is +loaded, we add a new instance of it to that increasingly long list. + +UI不再可见,但它也不会进入垃圾回收系统,因为角色的观察者列表还保存着对它的引用。每一次屏幕加载后,我们给那个不断增长的长列表添加一个新实例。 + +The entire time the player is playing the game, running around, and getting in +fights, the character is sending notifications that get received by *all* of +those screens. They aren't on screen, but they receive notifications and waste +CPU cycles updating invisible UI elements. If they do other things like play +sounds, you'll get noticeably wrong behavior. + +玩家玩游戏的整个时间,来回跑,打架,角色都会发送通知给*所有*这些屏幕。他们不再屏幕上,但他们接受通知浪费CPU循环在不可见的UI元素上。如果他们做了一些像播放声音的事情,你就得到了可见的错误行为。 + +This is such a common issue in notification systems that it has a name: the +*lapsed listener problem*. Since subjects retain +references to their listeners, you can end up with zombie UI objects lingering +in memory. The lesson here is to be disciplined about unregistration. + +这在通知系统中非常常见,甚至有个名字:*失效监听者问题*。由于客体保留了对监听者的引用,你最终有僵尸UI对象徘徊在内存中。这里上的一棵是有关于取消注册的纪律。 + + + +### What's going on? + +### 发生了什么? + +The other, deeper issue with the Observer pattern is a direct consequence of its +intended purpose. We use it because it helps us loosen the coupling between two +pieces of code. It lets a subject indirectly communicate with some observer +without being statically bound to it. + +另一个观察者的深层次问题是一个他打算的直接结果。我们使用它是因为它帮助我们放松了两块代码之间的耦合。他让一个客体不直接的与一些没有静态绑定到它的观察者交流。 + +This is a real win when you're trying to reason about the subject's behavior, +and any hangers-on would be an annoying distraction. If you're poking at the +physics engine, you really don't want your editor -- or your mind -- cluttered +up with a bunch of stuff about achievements. + +当你要让客体行为有意义的时候是很有价值的,任何悬挂件都是讨厌的注意力分散。如果你解接触物理引擎,你根本不想要你的编辑器——或者你的脑子——为一堆成就系统的东西而杂乱。 + +On the other hand, if your program isn't working and the bug spans some chain of +observers, reasoning about that communication flow is much more difficult. With +an explicit coupling, it's as easy as looking up the method being called. This +is child's play for your average IDE since the coupling is static. + +另一方面,如果你的程序没有工作,漏洞跨越了多个观察者,理清交流流程更加困难。通过显式耦合,它更容易查看那一个方法被调用了。这是由于耦合是静态的,这对于你的IDE是孩子的把戏。 + +But if that coupling happens through an observer list, the only way to tell who +will get notified is by seeing which observers happen to be in that list *at +runtime*. Instead of being able to *statically* reason about the communication +structure of the program, you have to reason about its *imperative, dynamic* +behavior. + +但是如果耦合发生在一个观察者列表中,唯一告诉你谁被通知的方法是看看哪个观察者在恰巧在列表中而且*处于运行*。不再是理清程序的*静态*交流结构,你得理清它的*命令式,动态*行为。 + +My guideline for how to cope with this is pretty simple. If you often need to +think about *both* sides of some communication in order to understand a part of +the program, don't use the Observer pattern to express that linkage. Prefer +something more explicit. + +我对如何处理这个的指导原则很简单。如果为了理解程序的一部分,沟通的两遍*都*需要被考虑。不要使用观察者模式表达这个连接。使用其他更加显式的东西。 + +When you're hacking on some big program, you tend to have lumps of it that you +work on all together. We have lots of terminology for this like "separation of +concerns" and "coherence and cohesion" and "modularity", but it boils down to +"this stuff goes together and doesn't go with this other stuff". + +当你在某些大型程序上用黑魔法时,你会感觉工作起来很笨拙。我们有很多术语给他,比如“角落分离”,“一致性和内聚性”和“模块化”,但是总归就是“这些东西待在一起而不与其他东西待在一起。” + +The observer pattern is a great way to let those mostly unrelated lumps talk to +each other without them merging into one big lump. It's less useful *within* a +single lump of code dedicated to one feature or aspect. + +观察者模式是一个让这些大量不相关的代码块互相角落而不必将他们打包成一个更大的块的好方法。这在专注于一个特性或层面的单一代码块*内*不会太有用。 + +That's why it fits our example well: achievements and physics are almost entirely +unrelated domains, likely implemented by different people. We want the bare +minimum of communication between them so that working on either one doesn't +require much knowledge of the other. + +这就是为什么它适应我们的例子:成就和物理是几乎完全不相干的领域,通常被不同人实现。我们想要他们之间最小化交流,这样物理在哪一个上工作都不需要另一个的太多消息。 + +## Observers Today + +## 今日观察者 + +*Design Patterns* came out in 1994. Back then, +object-oriented programming was *the* hot paradigm. Every programmer on Earth +wanted to "Learn OOP in 30 Days," and middle managers paid them based on the +number of classes they created. Engineers judged their mettle by the depth of +their inheritance hierarchies. + +*设计模式*源于1994.那时候,面向对象语言是*那个*热门范式。每一个程序员都想要“30天学会OOP”,中层管理员根据他们创建的类的数量为他们支付工资。工程师通过继承层次的深度评价质量。 + + + +The Observer pattern got popular during that zeitgeist, so it's no surprise that +it's class-heavy. But mainstream coders now are more comfortable with functional +programming. Having to implement an entire interface just to receive a +notification doesn't fit today's aesthetic. + +观察者模式在那个时代中很流行,所以它有很多类就不奇怪了。但是现代的主流程序员更加适应函数式语言。实现一整套接口只是为了接受一个通知不再适合今日的美学了。 + +It feels heavyweight and rigid. It *is* +heavyweight and rigid. For example, you can't have a single class that uses +different notification methods for different subjects. + +它感觉是又沉重又死板。它*确实*又沉重又死板。举个例子,你不能有一个类为不同的对象使用不同的通知方法。 + + + +A more modern approach is for an "observer" to be only a reference to a method +or function. In languages with first-class functions, and especially ones with +closures, this is a much more common way to do +observers. + +一个现代的解决办法是让一个“观察者”只是对方法或者函数的引用。在函数作为第一公民的与扬中,特别是那些有闭包的,这是一个更加普遍的方式实现观察者。 + + + +For example, C# has "events" baked into the language. With those, the observer +you register is a "delegate", which is that language's term for a reference to a +method. In JavaScript's event system, observers *can* be objects supporting a +special `EventListener` protocol, but they can also just be functions. The +latter is almost always what people use. + +举个例子,C#有“事件”嵌在语言中。通过这些,你注册的观察者是一个“代表”,这是语言的术语描述一个方法的引用。在JavaScript事件系统中,观察者*可以*是支持了特定`EventListener`协议的类,但是他们也只能是函数。后者是人们通常用的。 + +If I were designing an observer system today, I'd make it function-based instead of class-based. Even in C++, I +would tend toward a system that let you register member function pointers as +observers instead of instances of some `Observer` interface. + +如果我现在设计一个观察者模式,我会让他成为基于函数的而不是基于类的。哪怕是在C++,我倾向于一个让你注册一个成员函数指针作为一个观察者,而不是`Observer`接口的实例。 + + + +## Observers Tomorrow + +## 明日观察者 + +Event systems and other observer-like patterns are incredibly common these days. +They're a well-worn path. But if you write a few large apps using them, you +start to notice something. A lot of the code in your observers ends up looking +the same. It's usually something like: + +事件系统和其他类观察者模式在今日令人惊奇的多。他们都是经典方法。但是如果你用他们写一个稍微大一些的引用,你会发现一件事情。在你观察者中很多代码最后都长得一样。通常是像这样: + + 1. Get notified that some state has changed. + + 2. Imperatively modify some chunk of UI to reflect the new state. + + 1. 获知有状态改变了。 + + 2. 命令式的改变一些UI来反映新的状态。 + +It's all, "Oh, the hero health is 7 now? Let me set the width of the health bar +to 70 pixels." After a while, that gets pretty tedious. Computer science +academics and software engineers have been trying to eliminate that tedium for a +*long* time. Their attempts have gone under a number of different names: +"dataflow programming", "functional reactive programming", etc. + +这就是全部了,“哦,英雄的健康现在是7了?让我们把血条的宽度设为70像素。”过上一段时间,这会变得很沉闷。计算机科学学术界和软件工程师已经尝试结束这种沉闷*很长*时间了。他们用不同的方式做了很多次:“数据流编程”,“函数反射编程”等等。 + +While there have been some successes, usually in limited domains like audio +processing or chip design, the Holy Grail still hasn't been found. In the +meantime, a less ambitious approach has started gaining traction. Many recent +application frameworks now use "data binding". + +如果有突破,一般局限在特定的领域比如音频处理货芯片设计,圣杯还没有被找到。与此同时,一个更少野心的方式开始获得成效。很多最近的应用框架现在使用“数据绑定”。 + +Unlike more radical models, data binding doesn't try to entirely eliminate +imperative code and doesn't try to architect your entire application around a +giant declarative dataflow graph. What it does do is automate the busywork where +you're tweaking a UI element or calculated property to reflect a change to some +value. + +不像激进的模型,数据绑定不再指着完全终结命令式代码,也不尝试架构你的整个应用在一个巨大的宣言式数据图表。他做的就是自动完成你将一个UI元素或者计算结果来反映一些数据的改变。 + +Like other declarative systems, data binding is probably a bit too slow and +complex to fit inside the core of a game engine. But I would be surprised if I +didn't see it start making inroads into less critical areas of the game like +UI. + +就像其他宣言式系统,数据绑定也许太慢太复杂来适应游戏引擎的核心。但是如果说没看到它开始侵入游戏不那么性能攸关的部分比如UI那我会很惊讶。 + +In the meantime, the good old Observer pattern will still be here waiting for +us. Sure, it's not as exciting as some hot technique that manages to cram both +"functional" and "reactive" in its name, but it's dead simple and it works. To +me, those are often the two most important criteria for a solution. + +与此同时,好的老观察者模式仍然在那里等着我们。是的,他不想其他的新热门技术一样填满了“函数”“反射”在他的名字中,但是它超简单而且能工作。对于我来说,这通常是一个解决方案最重要的条件。 diff --git a/book/optimization-patterns.markdown b/book/optimization-patterns.markdown new file mode 100644 index 0000000..63016e4 --- /dev/null +++ b/book/optimization-patterns.markdown @@ -0,0 +1,35 @@ +^title Optimization Patterns + +优化模式 + +While the rising tide of faster and faster hardware has lifted most software +above worrying about performance, games are one of the few remaining exceptions. +Players always want richer, more realistic and exciting experiences. Screens are +crowded with games vying for a player's attention -- and cash! -- and the game +that pushes the hardware the furthest often wins. + +虽然越来越快的硬件解除了大部分软件在性能上的顾虑,游戏是其中的一个特例。玩家总是想要更丰富的,更真实的,更激动人心的体验。屏幕上满是争抢玩家注意力——还有金钱——的游戏,将硬件的功能发挥至极致的游戏往往获胜。 + +Optimizing for performance is a deep art that touches all aspects of software. +Low-level coders master the myriad idiosyncrasies of hardware architectures. +Meanwhile, algorithms researchers compete to prove mathematically whose +procedure is the most efficient. + +优化游戏性能是一门高深的艺术,接触到软件的各个层面。底层的程序员掌握硬件架构的种种特质。同时,算法研究者争先恐后的证明谁的过程是最有效率的。 + +Here, I touch on a few mid-level patterns that are often used to speed up a +game. [Data Locality](data-locality.html) introduces you to the modern +computer's memory hierarchy and how you can use it to your advantage. The [Dirty +Flag](dirty-flag.html) pattern helps you avoid unnecessary computation while +[Object Pools](object-pool.html) help you avoid unnecessary allocation. [Spatial +Partitioning](spatial-partition.html) speeds up the virtual world and its +inhabitants' arrangement in space. + +这里,我描述了几个加速游戏的中间层模式。[数据局部性](data-locality.html)向你介绍了计算机的存储层次以及如何使用其以获得优势。[脏标志](dirty-flag.html)模式帮你避开不必要的计算,[对象池](object-pool.html)帮你避开不必要的分配。 [空间分区](spatial-partition.html)加速了虚拟世界和其中居民的空间布局。 + +## The Patterns + +* [Data Locality](data-locality.html) +* [Dirty Flag](dirty-flag.html) +* [Object Pool](object-pool.html) +* [Spatial Partition](spatial-partition.html) diff --git a/book/prototype.markdown b/book/prototype.markdown new file mode 100644 index 0000000..274f4bb --- /dev/null +++ b/book/prototype.markdown @@ -0,0 +1,786 @@ +^title Prototype +^section Design Patterns Revisited + +The first time I heard the word "prototype" was in *Design Patterns*. Today, it +seems like everyone is saying it, but it turns out they aren't talking about the +design pattern. We'll cover that here, but I'll also +show you other, more interesting places where the term "prototype" and the +concepts behind it have popped up. But first, let's revisit the original pattern. + +第一次我听到“原型”这个词是在*设计模式*中。今天,似乎每个人都在谈论它,谈事他们讨论的不是设计模式。我们等会会覆盖它,但是我同样会展示给你另一个,更加有趣的“原型”使用的地方以及他出现的背后观念。但是首先,让我们重访原型的设计模式。 + + + +## The Prototype Design Pattern + +## 原型设计模式 + +Pretend we're making a game in the style of Gauntlet. We've got creatures and +fiends swarming around the hero, vying for their share of his flesh. These +unsavory dinner companions enter the arena by way of "spawners", and there is a +different spawner for each kind of enemy. + +假设我们要用Gauntlet的风格做一款游戏。我们有动物和魔鬼蜂群围绕着英雄,竞争分享他的血肉。这些不可口的晚餐同伴通过“产卵者”进入这片区域,这对于每一种敌人有不同的产卵者。 + +For the sake of this example, let's say we have different classes for each kind +of monster in the game -- `Ghost`, `Demon`, `Sorcerer`, etc., like: + +为了这个例子的利益,假设我们队每一种游戏中的怪物都有不同的类——`Ghost`,`Demon`,`Sorcerer`等等,像这样: + +^code monster-classes + +A spawner constructs instances of one particular monster type. To support every +monster in the game, we *could* brute-force it by having a spawner class for +each monster class, leading to a parallel class hierarchy: + +一个产卵者构造特定种类的怪物的实例。为了在游戏中支持每一种怪物,我们*可以*残忍的强迫它,让每一个怪物类都有一个产卵者类,,得到了一个平行的类层次。 + + + +Parallel class hierarchies. Ghost, Demon, and Sorceror all inherit from Monster. GhostSpawner, DemonSpawner, and SorcerorSpawner inherit from Spawner. + + + +Implementing it would look like this: + +实现这个看起来像是这个样: + +^code spawner-classes + +Unless you get paid by the line of code, this is obviously not a fun way +to hack this together. Lots of classes, lots of boilerplate, lots of redundancy, +lots of duplication, lots of repeating myself... + +除非你会因为这行代码获得工资,这很明显不是有趣的方法将这些焊在一起。众多类,众多引用,众多冗余,众多副本,众多重复自我…… + +The Prototype pattern offers a solution. The key idea is that *an object can +spawn other objects similar to itself*. If you have one ghost, you can make more +ghosts from it. If you have a demon, you can make other demons. Any monster can +be treated as a *prototypal* monster used to generate other versions of +itself. + +原型模式提供了一个解决方案。关键注意是*一个对象可以产出与它自己相近的对象。*如果你有一个鬼影,你可以从他制造更多的鬼影。如果你有一个恶魔,你可以制造其他恶魔。任何怪物都可以被视为*原型*怪物来产出它自己的其他版本。 + +To implement this, we give our base class, `Monster`, an abstract `clone()` +method: + +为了实现这一点,我们给出了我们的基类`Monster`和一个抽象方法`clone()`: + +^code virtual-clone + +Each monster subclass provides an implementation that returns a new object +identical in class and state to itself. For example: + +每一个怪兽子类提供一个实现,返回一个新的对象与他自己的类和状态都完全一样。举个例子: + +^code clone-ghost + +Once all our monsters support that, we no longer need a spawner class for each +monster class. Instead, we define a single one: + +一旦我们所有的怪物都支持那个,我们不需要一个为每一个怪物类制作一个产卵类。取而代之的是,我们只定义一个: + +^code spawner-clone + +It internally holds a monster, a hidden one whose sole purpose is to be used by +the spawner as a template to stamp out more monsters like it, sort of like a +queen bee who never leaves the hive. + +它内部有保存一个怪物,一个隐藏的怪物,唯一的目的就是被产卵者作为模板去产生更多像他一样的怪物,有点像一个从来不离开巢穴的蜂后。 + +A Spawner contains a prototype field referencing a Monster. It calls clone() on the prototype to create new monsters. + +To create a ghost spawner, we create a prototypal ghost instance and +then create a spawner holding that prototype: + +为了创造一个鬼魂产出者,我们创建一个原型鬼魂实例然后创建一个产出者拥有这个实例: + +^code spawn-ghost-clone + +One neat part about this pattern is that it doesn't just clone the *class* of +the prototype, it clones its *state* too. This means we could make a spawner for +fast ghosts, weak ghosts, or slow ghosts just by creating an appropriate +prototype ghost. + +我们这个模式的灵巧部分是它不但拷贝原型的*类*,也拷贝它的*状态*。这就意味着我们可以创建一个产出者生产快速的鬼魂,虚弱的鬼魂,或者慢速的鬼魂,只需要创建一个合适的原型鬼魂。 + +I find something both elegant and yet surprising about this pattern. I can't +imagine coming up with it myself, but I can't imagine *not* knowing about it now +that I do. + +我在这个模式中找到了一些既优雅又惊讶的东西。我无法相信我找到的东西,但我更无法想象*不知道*这些东西的现在的自己。 + +### How well does it work? + +### 它工作的如何? + +Well, we don't have to create a separate spawner class for each monster, so +that's good. But we *do* have to implement `clone()` in each monster class. +That's just about as much code as the spawners. + +好吧,我们不需要为每一个怪物创建一个单独 产出者类,那很好。但我们*确实*需要在每个怪物类中实现实现`clone()`。这和产卵者中的代码一样多。 + +There are also some nasty semantic ratholes when you sit down to try to write a +correct `clone()`. Does it do a deep clone or shallow one? In other words, if a +demon is holding a pitchfork, does cloning the demon clone the pitchfork too? + +这里有些令人不快的语义漏洞,当你坐下来试着写一个正确的`clone()`。做一个深层拷贝还是浅层的呢?换言之,如果一个恶魔拿着草叉,克隆恶魔也要克隆草叉吗? + +Also, not only does this not look like it's saving us much code in this +contrived problem, there's the fact that it's a *contrived problem*. We had to +take as a given that we have separate classes for each monster. These days, +that's definitely *not* the way most game engines roll. + +同时,这看上去既不减少我们在已存问题上的代码,这里事实上还有一些*人为的问题*。我们需要将每个怪物有独立的类作为前提条件。最近的日子,这绝对*不是*大多数游戏引擎运转的方法。 + +Most of us learned the hard way that big class hierarchies like this are a pain +to manage, which is why we instead use patterns like Component and Type Object to model different kinds of entities without +enshrining each in its own class. + +我们中的大多数以痛苦的方式学到这样巨大的类层次管理起来是很痛苦的,那就是我们为什么用组件模式和类型对象来为不同种类的实体建模,无需让他们每一个都有自己的类。 + +### Spawn functions + +### 产卵函数 + +Even if we do have different classes for each monster, there are other ways to +decorticate this *Felis catus*. Instead of making separate spawner *classes* for +each monster, we could make spawn *functions*, like so: + +哪怕我们确实为每一怪物都有不同的类,这里有其他方式这里还有其他方式来为*家猫*剥皮。不是使用为每一个怪物建立分离的产卵*类*,我们可以创建产卵*函数*,就像这样: + +^code callback + +This is less boilerplate than rolling a whole class for constructing a monster +of some type. Then the one spawner class can simply store a function pointer: + +这比将不同类型怪兽的构建包到一个有更少的样板。 + +^code spawner-callback + +To create a spawner for ghosts, you do: + +为了给鬼魂创建一个产卵者,你需要做: + +^code spawn-ghost-callback + +### Templates + +### 模板 + +By now, most C++ developers are familiar with +templates. Our spawner class needs to construct instances of some type, but we +don't want to hard code some specific monster class. The natural solution then +is to make it a *type parameter*, which templates let us do: + +到了现在,大多数C++开发者很熟悉模板了。我们的产卵者类需要构建各种类型的实例,但是五门不想Wie特定的怪物类硬编码。自然的解决方案是将它作为*类型参数*,就是模板让我们做的: + + + + + +^code templates + +Using it looks like: + +像这样使用它: + +^code use-templates + + + +### First-class types + +### 第一公民类型 + +The previous two solutions address the need to have a class, `Spawner`, which is +parameterized by a type. In C++, types aren't generally first-class, so that +requires some gymnastics. If you're using a +dynamically-typed language like JavaScript, Python, or Ruby where classes *are* + regular objects you can pass around, you can solve this much more directly. + +前面的两个解决方案完成了需要一个类的需求,`Spawner`,那个以类型作为参数haunted的东西。在C++中,类型不是第一公民,所以需要一些艺术。如果你使用了一个动态类型语言比如JavaScript,Python,或者Ruby,他们的类*是*你可以传递的对象,你可以更加直接的解决这个问题。 + + + +When you make a spawner, just pass in the class of monster that it should +construct -- the actual runtime object that represents the monster's +class. Easy as pie. + +当你完成一个产出者,直接向他传递一个它应该构建的怪物的类型——那个真实的运行时对象,代表了怪物的类。小菜一碟。 + +With all of these options, I honestly can't say I've found a case where I felt +the Prototype *design pattern* was the best answer. Maybe your experience will +be different, but for now let's put that away and talk about something else: +prototypes as a *language paradigm*. + +使用这些选项,我不能诚实的说我找到了一种情况,原型*设计模式*是最好的方案。也许你的经验有所不同,但是现在把它放到一边,讨论点别的:原型作为一种*语言范式*。 + +## The Prototype Language Paradigm + +## 原型语言范式 + +Many people think "object-oriented programming" is synonymous with "classes". +Definitions of OOP tend to feel like credos of opposing religious denominations, +but a fairly non-contentious take on it is that *OOP lets you define "objects" +which bundle data and code together.* Compared to structured languages like C +and functional languages like Scheme, the defining characteristic of OOP is that +it tightly binds state and behavior together. + +很多人认为“面向对象编程”和“类”是同义词。OOP的定义感觉像是相反宗教信仰的教义,公平无争议的是*OOP让你定义“对象”,将数据和代码绑定在一起。*与结构化的语言比如C相比,与函数语言比如Scheme相比,OOP的定义特性是它将状态和行为紧紧地绑在一起。 + +You may think classes are the one and only way to do that, but a handful of guys +including Dave Ungar and Randall Smith beg to differ. They created a language in +the 80s called Self. While as OOP as can be, it has no classes. + +你也许认为类是唯一可以完成这个的方法,但是一大堆家伙包括Dave Ungar和Randall Smith一直拼命区分。他们在80年代床架了一种语言叫做Self。它可以是OOP的,同时没有类 + +### Self + +### Self语言 + +In a pure sense, Self is *more* object-oriented than a class-based language. We +think of OOP as marrying state and behavior, but languages with classes actually +have a line of separation between them. + +纯粹的感觉上,Self比一个基于类的语言*更加*面向对象。我们人为OOP是将状态和行为绑在一起,但是有类的于洋实际在他们之间画了一条线。 + +Consider the semantics of your favorite class-based language. To access some +state on an object, you look in the memory of the instance itself. State is +*contained* in the instance. + +考虑你最喜欢的基于类的语言语义。为了接触一个对象中的一些状态,你查询内存中实例。状态是*包含*在实例之中的。 + +To invoke a method, though, you look up the +instance's class, and then you look up the method *there*. Behavior is contained +in the *class*. There's always that level of indirection to get to a method, +which means fields and methods are different. + +但是,为了调用一个方法,你查询实例的类,然后你在*那里*查询方法。行为被包含在*类*中。这里总有一次跳转来获得一个方法,这意味着字段和方法是不同的。 + +A Class contains a list of Methods. An Instance contains a list of Fields and a reference to its Class. + + + +Self eliminates that distinction. To look up *anything*, you just look on the +object. An instance can contain both state and behavior. You can have a single +object that has a method completely unique to it. + +Self结束了这种分歧。找*任何东西*,你需要在对象中找。一个实例同时包含状态和行为。你可以有一个对象,他有一个完全独特的方法。 + + + +An Object contains a mixed list of Fields and Methods. + + + +If that was all Self did, it would be hard to use. Inheritance in class-based +languages, despite its faults, gives you a useful mechanism for reusing +polymorphic code and avoiding duplication. To accomplish something similar +without classes, Self has *delegation*. + +如果这就是Self做的,那它很难使用。在基于类的语言中继承,不管他的缺陷,给你了有用的机制来重用多态代码和避免复制。为了不用类而实现一些相同的东西,Self拥有*授权*。 + +To find a field or call a method on some object, we first look in the object +itself. If it has it, we're done. If it doesn't, we look at the object's *parent*. This is just a reference to some other object. +When we fail to find a property on the first object, we try its parent, and its +parent, and so on. In other words, failed lookups are *delegated* to an object's +parent. + +为了在对象中寻找字段或者调用方法,我们首先在对象自己里查找。如果它有,我们就完成了。如果它没有,我吗在对象的*父类*中寻找。这是一个对其他对象的引用。当我们没能在第一个对象中找到属性,我们尝试它的父母,然后父母的父母,继续下去。换言之,失败的查找被*授权*给对象的父母。 + + + +An Object contains Fields and Methods and a reference to another object that it delegates to. + +Parent objects let us reuse behavior (and state!) across multiple objects, so +we've covered part of the utility of classes. The other key thing classes do is +give us a way to create instances. When you need a new thingamabob, you can just +do `new Thingamabob()`, or whatever your preferred language's syntax is. A class +is a factory for instances of itself. + +父母对象让我们在不同对象间重用行为(还有状态!),所以我们覆盖了类的公共部分。另外一个类做的关键事情就是给了我们一种方法来创建实例。但你需要一个新的某物,你可以直接`new Thingamabob()`,或者无论什么你喜欢的语言表达法。一个类是实例本身的工厂。 + +Without classes, how do we make new things? In particular, how do we make a +bunch of new things that all have stuff in common? Just like the design pattern, +the way you do this in Self is by *cloning*. + +不用类,我们怎样创建新东西?特别的,我们如何创建一堆所有东西都是一样的新东西?就像设计模式,你在Self中的方式是使用*克隆*。 + +In Self, it's as if *every* object supports the Prototype design pattern +automatically. Any object can be cloned. To make a bunch of similar objects, you: + +在Self中,就好像*每一个*对象都自动支持原型设计模式。任何对象都能被克隆。为了获得一堆相似的对象,你: + +1. Beat one object into the shape you want. You can just clone the base `Object` + built into the system and then stuff fields and methods into it. + +2. Clone it to make as many... uh... clones as you want. + +1. 讲一个对象拍打成你想要的形状。你可以直接克隆系统内建的基本`Object`,然后向其中添加字段和方法。 + +2. 克隆它来产出……额……你想要的克隆数量。 + +This gives us the elegance of the Prototype design pattern without the tedium of +having to implement `clone()` ourselves; it's built into the system. + +这就给了我们原型模式的优雅而无需我们自己沉闷的实现`clone()`;它被内建子啊系统中。 + +This is such a beautiful, clever, minimal system that as soon as I learned about +it, I started creating a prototype-based language to get more experience with it. + +这是一个如此美妙,紧密最小化的系统,当我一听说它,我就开始创建一个基于原型的语言来获取更多经验。 + + + +### How did it go? + +### 它运行的如何? + +I was super excited to play with a pure prototype-based language, but once I had +mine up and running, I discovered an unpleasant fact: +it just wasn't that fun to program in. + +我急切的想要使用一个纯粹的基于原型的语言,但是一旦我完成了并运行它,我发现了一个不愉快的事实:在其上编程没有那样有趣。 + + + +Sure, the language was simple to implement, but that was because it punted the +complexity onto the user. As soon as I started trying to use it, I found myself +missing the structure that classes give. I ended up trying to recapitulate it at +the library level since the language didn't have it. + +是的,语言很容易实现,那是因为它把复杂度推给了用户。一旦我们开始试着使用它,我发现我想念类给的结构。在库级别我最终停止了概括它,因为语言并没有。 + +Maybe this is because my prior experience is in class-based languages, so +my mind has been tainted by that paradigm. But my hunch is that most people just +like well-defined "kinds of things". + +谢雨这是因为我之前的经验是在基于类的语言上,所以我的头脑被那个范式污染了。但是我的直觉是大部分人还是喜欢好好定义的“那些事物”。 + +In addition to the runaway success of class-based languages, look at how many +games have explicit character classes and a precise roster of different sorts +of enemies, items, and skills, each neatly labeled. You don't see many games +where each monster is a unique snowflake, like "sort of halfway between a troll +and a goblin with a bit of snake mixed in". + +除去基于类的语言的成功,看看有多少游戏明确指定了玩家类和不同种的敌人物品技能的清单,每一个都有整齐的标签。你你在不会再游戏中看到每一个怪物都是独立的雪团,像“洞穴人和哥布林还有一些雪混合在一起”的东西。 + +While prototypes are a really cool paradigm and one that I wish more people +knew about, I'm glad that most of us aren't actually programming using them +every day. The code I've seen that fully embraces +prototypes has a weird mushiness to it that I find hard to wrap my head around. + +原型是非常酷的范式,也是我希望有更多人了解的东西,我很庆幸我们不是每天都用他们编程。我看到的完全皈依原型的语言是一团浆糊,我很难用它完成点什么。 + + + +### What about JavaScript? + +### JavaScript又怎么样呢? + +OK, if prototype-based languages are so unfriendly, how do I explain JavaScript? +Here's a language with prototypes used by millions of people every day. More +computers run JavaScript than any other language on Earth. + +好吧,如果基于原型的于洋是不那么友好,我怎么解释JavaScript呢?这是一个有原型的语言,每天都被数百万人使用。运行JavaScript的机器数量超过了地球上其他所有的语言。 + +Brendan Eich, the creator of JavaScript, took +inspiration directly from Self, and many of JavaScript's semantics are +prototype-based. Each object can have an arbitrary set of properties, both +fields and "methods" (which are really just functions stored as fields). An +object can also have another object, called its "prototype", that it delegates +to if a field access fails. + +Brendan Eich,JavaScript的缔造者,从Self中直接吸收灵感,很多JavaScript的语义都是基于原型的。每一个对象都有任意集合的特性,字段和“方法”(事实上只是存储为字段的函数)都是如此。一个对象可以拥有另一个对象,被称为它的“原型”,如果字段获取失败就会获得它。 + + + +But, despite that, I believe that JavaScript in practice has more in common with +class-based languages than with prototypal ones. One hint that JavaScript has +taken steps away from Self is that the core operation in a prototype-based +language, *cloning*, is nowhere to be seen. + +但是不管那个,我相信JavaScript在事件中更像是基于类的而不是基于原型的。一个关键点是JavaScript将一些基于原型的语言核心操作取了出来,*克隆*,不见了。 + +There is no method to clone an object in JavaScript. The closest it has is +`Object.create()`, which lets you create a new object that +delegates to an existing one. Even that wasn't added until ECMAScript 5, +fourteen years after JavaScript came out. Instead of cloning, let me walk you +through the typical way you define types and create objects in JavaScript. You +start with a *constructor function*: + +在JavaScript中没有方法来克隆一个对象。最接近的是`Object.create()`,允许你创建一个新的对象代表现有的。那个在ECMAScript5之前都没有添加,那是JavaScript出现后的十四年了。不用克隆,让我带你浏览一下JavaScript中in定义类和创建对象的经典方法。从一个*构造器函数*开始: + + :::javascript + function Weapon(range, damage) { + this.range = range; + this.damage = damage; + } + +This creates a new object and initializes its fields. You invoke it like: + +这创建了一个新对象并初始化了它的字段。你像这样引入它: + + :::javascript + var sword = new Weapon(10, 16); + +The `new` here invokes the body of the `Weapon()` function with `this` bound to a +new empty object. The body adds a bunch of fields to it, then the now-filled-in +object is automatically returned. + +这里的`new`引入了`Weapon()`函数的实体,而`this`绑定到一个新的空对象上。实体添加了一系列字段到它上面,然后现在填满的对象自动返回了。 + +The `new` also does one other thing for you. When it creates that blank object, +it wires it up to delegate to a prototype object. You can get to that object +directly using `Weapon.prototype`. + +`new`同样为你做了另外一件事情。当他创建那个空对象,它将它和一个原型对象连接起来了。你可以用`Weapon。prototype`来直接使用它。 + +While state is added in the constructor body, to define *behavior*, you usually +add methods to the prototype object. Something like this: + +当状态被添加到构建器中,为了定义*行为*,你通常为原型对象添加方法。就像这样: + + :::javascript + Weapon.prototype.attack = function(target) { + if (distanceTo(target) > this.range) { + console.log("Out of range!"); + } else { + target.health -= this.damage; + } + } + +This adds an `attack` property to the weapon prototype whose value is a +function. Since every object returned by `new Weapon()` delegates to +`Weapon.prototype`, you can now call `sword.attack()` and it will call that +function. It looks a bit like this: + +这添加了一个`attack`属性给武器原型,值是一个函数。由于每一个对象都通过代表`Weapon.prototype`的`new Weapon()`返回,你现在可以调用一个`sword.attack()`,他会调用那个函数。这看上去像是这样: + +A Weapon object contains an attack() method and other methods. A Sword object contains fields and delegates to Weapon. + +Let's review: + +让我们回顾一下: + +* The way you create objects is by a "new" operation that you invoke using an + object that represents the type -- the constructor function. + +* 你创建对象的方法是通过“new”操作,引入一个代表类型的对象——构造器函数。 + +* State is stored on the instance itself. + +* 状态被存储在实例本身中。 + +* Behavior goes through a level of indirection -- delegating to the prototype -- + and is stored on a separate object that represents the set of methods shared + by all objects of a certain type. + +* 行为通过系列跳转——指向原型的代表——被存储在一个分散的对象中,代表了一系列特定类型的对象共享的方法。 + +Call me crazy, but that sounds a lot like my description of classes earlier. You +*can* write prototype-style code in JavaScript (*sans* cloning), but the syntax +and idioms of the language encourage a class-based approach. + +说我疯了吧,但这听起来很像是我之前描述的类。你*可以*写原型风格的代码在JavaScript中(*没有*克隆),但是语言的语义和通常方法推荐一个基于类的方法。 + +Personally, I think that's a good thing. Like I said, I +find doubling down on prototypes makes code harder to work with, so I like that +JavaScript wraps the core semantics in something a little more classy. + +个人而言,我认为这是一个好事。就像我说的,我发现完全使用原型让代码很难处理,所以我喜欢JavaScript间整个核心语义包裹在一些更漂亮的东西中。 + +## Prototypes for Data Modeling + +## 为数据模型建立原型 + +OK, I keep talking about things I *don't* like prototypes for, which is making +this chapter a real downer. I think of this book as more comedy than tragedy, so +let's close this out with an area where I *do* think prototypes, or more +specifically *delegation*, can be useful. + +好吧,我不断的讨论我*不喜欢*原型的原因,这让这一章有些黯淡。我认为这本书该比惨案更加欢乐些,所以让我们结束这个话题在我*确实*考虑原型的地方,或者更加精确的,*委托*,是有用的。 + +If you were to count all the bytes in a game that are code compared to the ones +that are data, you'd see the fraction of data has been increasing steadily since +the dawn of programming. Early games procedurally generated almost everything so +they could fit on floppies and old game cartridges. In many games today, the +code is just an "engine" that drives the game, which is defined entirely in +data. + +如果你想要计算中游戏的字节数并与数据的相比,你可以看到游戏的部分随着程序的进行稳定的增长。早期的游戏在程序中生成几乎任何东西,这样他们可以适应磁盘和老式游戏盒。子啊今日的游戏中,代码只是驱动游戏的“引擎”,游戏是完全由数据定义的。 + +That's great, but pushing piles of content into data files doesn't magically +solve the organizational challenges of a large project. If anything, it makes it +harder. The reason we use programming languages is because they have tools for +managing complexity. + +这很好,但是将内容推到数据文件中不会魔法般的解决组织大工程的挑战。如果有什么区别的话,它让它更难了。我们使用编程语言的原因就是他们有办法处理复杂性。 + +Instead of copying and pasting a chunk of code in ten places, we move it into a +function that we can call by name. Instead of copying a method in a bunch of +classes, we can put it in a separate class that those classes inherit from or +mix in. + +不是讲一堆代码拷来拷去,我们将其移入一个函数中,我们可以通过名字调用。不是在一堆类之间复制方法,我可以将其放入分离的类中,其他类可以从它继承或者把它混合进来。 + +When your game's data reaches a certain size, you really start wanting similar +features. Data modeling is a deep subject that I can't hope to do justice here, +but I do want to throw out one feature for you to consider in your own games: +using prototypes and delegation for reusing data. + +当你的游戏数据达到一定规模时,你真的需要考虑更加简单的特性。数据模式是一个我不能指望在这里能说清的问题,但是我确实希望将一个特性扔给你在你的游戏中考虑:使用原型和委托来重要数据。 + +Let's say we're defining the data model for the shameless +Gauntlet rip-off I mentioned earlier. The game designers need to specify +the attributes for monsters and items in some kind of files. + +让我们假设我们为我早先提到的无耻挑战定义数据模型。游戏设计者需要特定怪物和事物在很多文件中定义它们的属性。 + + + +One common approach is to use JSON. Data entities are basically *maps*, or +*property bags*, or any of a dozen other terms because there's nothing +programmers like more than inventing a new name +for something that already has one. + +一个通用的方法是使用JSON。数据实体是基本“图”,或者“属性包”,或者其他什么术语,因为程序员就喜欢为已有的东西发明新名字。 + + + +So a goblin in the game might be defined something like this: + +所以游戏中的一个哥布林也许被定义为像这样的东西: + + :::json + { + "name": "goblin grunt", + "minHealth": 20, + "maxHealth": 30, + "resists": ["cold", "poison"], + "weaknesses": ["fire", "light"] + } + +This is pretty straightforward and even the most text-averse designer can handle +that. So you throw in a couple of sibling branches on the Great Goblin Family +Tree: + +这看上去很直观,哪怕是最讨厌文字的设计者也能完成。所以你给哥布林大家族添加几个兄弟分支: + + :::json + { + "name": "goblin wizard", + "minHealth": 20, + "maxHealth": 30, + "resists": ["cold", "poison"], + "weaknesses": ["fire", "light"], + "spells": ["fire ball", "lightning bolt"] + } + + { + "name": "goblin archer", + "minHealth": 20, + "maxHealth": 30, + "resists": ["cold", "poison"], + "weaknesses": ["fire", "light"], + "attacks": ["short bow"] + } + +Now, if this was code, our aesthetic sense would be tingling. There's a lot of +duplication between these entities, and well-trained programmers *hate* that. It +wastes space and takes more time to author. You have to read carefully to tell +if the data even *is* the same. It's a maintenance headache. If we decide to +make all of the goblins in the game stronger, we need to remember to update the +health of all three of them. Bad bad bad. + +现在,如果这是代码,我们的美感就被刺激到了。这里在实体间有很多的重复性,训练优良的程序员*讨厌*它。它浪费了空间,消耗了作者更多时间。你需要仔细阅读代码才知道这些数据*是不是*相同的。这让维护头疼。如果我们决定让所有的哥布林在游戏中变强,我们需要记住为他们三个都更新一遍。糟糕糟糕糟糕。 + +If this was code, we'd create an abstraction for a "goblin" and reuse that +across the three goblin types. But dumb JSON doesn't know anything about that. +So let's make it a bit smarter. + +如果这是代码,我们会为“哥布林”创造一个抽象,然后子啊是那种哥布林类型中重用他们。但是愚蠢的JSON不知道这么做。素以让我们把它做得更加巧妙。 + +We'll declare that if an object has a `"prototype"` +field, then that defines the name of another object that this one delegates to. +Any properties that don't exist on the first object fall back to being looked up +on the prototype. + +我们声明它好像一个对象有“原型”字段,然后定义这个对象委托给的另一个对象。任何不在第一个对象中的原型都退回在原型中查找。 + + + +With that, we can simplify the JSON for our goblin horde: + +通过这样,我们可以简单的定义一大群哥布林: + + :::json + { + "name": "goblin grunt", + "minHealth": 20, + "maxHealth": 30, + "resists": ["cold", "poison"], + "weaknesses": ["fire", "light"] + } + + { + "name": "goblin wizard", + "prototype": "goblin grunt", + "spells": ["fire ball", "lightning bolt"] + } + + { + "name": "goblin archer", + "prototype": "goblin grunt", + "attacks": ["short bow"] + } + +Since the archer and wizard have the grunt as their prototype, we don't have to +repeat the health, resists, and weaknesses in each of them. The logic we've added +to our data model is super simple -- basic single delegation -- but we've +already gotten rid of a bunch of duplication. + +由于弓箭手有他们grunt作为原型,我们不需要在他们中重复健康,防御和弱点。我们给数据增加的新逻辑超简单——基本的单一委托——但是我们已经摆脱了一堆重复的东西。 + +One interesting thing to note here is that we didn't set up a fourth "base +goblin" *abstract* prototype for the three concrete goblin types to delegate to. +Instead, we just picked one of the goblins who was the simplest and delegated to +it. + +一个有趣的事情是我们没有为这三个具体的哥布林委托的*抽象*原型设置任何“基本哥布林”。相反,我们选择了一个最简单的哥布林,然后委托给它。 + +That feels natural in a prototype-based system where any object can be used as a +clone to create new refined objects, and I think it's equally natural here too. +It's a particularly good fit for data in games where you often have one-off +special entities in the game world. + +在一个基于原型的系统中,当一个对象可以被克隆产生一个新对象是很自然的,而我认为在这里也一样自然。这特别适合于那些游戏世界中你有一项特殊之处的实体的数据。 + +Think about bosses and unique items. These are often refinements of a more +common object in the game, and prototypal delegation is a good fit for defining +those. The magic Sword of Head-Detaching, which is really just a longsword with +some bonuses, can be expressed as that directly: + +考虑Boss和独特的事物,他们通常是更加平凡事物的重新定义,而原型委托是一个定义他们的好方法。断头魔剑,就是一个拥有加成的长剑,可以向下面这样表示: + + :::json + { + "name": "Sword of Head-Detaching", + "prototype": "longsword", + "damageBonus": "20" + } + +A little extra power in your game engine's data modeling system can make it +easier for designers to add lots of little variations to the armaments and +beasties populating your game world, and that richness is exactly what delights +players. + +游戏引擎中一点额外的能力就能让设计者更加方便地添加多种不同的武器和游戏世界中的动物,这种丰富度会取悦玩家。 diff --git a/book/sequencing-patterns.markdown b/book/sequencing-patterns.markdown new file mode 100644 index 0000000..25abea0 --- /dev/null +++ b/book/sequencing-patterns.markdown @@ -0,0 +1,31 @@ +^title Sequencing Patterns + +序列模式 + +Videogames are exciting in large part because they take us somewhere else. For a +few minutes (or, let's be honest with ourselves, much longer), we become +inhabitants of a virtual world. Creating these worlds is one of the supreme +delights of being a game programmer. + +电子游戏之所有有趣,很大程度上归功于他们会将我们带到别的地方。几分钟后(或者,诚实点,可能会更长),我们栖息在一个虚拟的世界。创造那样一个世界是游戏程序员至上的欢愉。 + +One aspect that most of these game worlds feature is *time* -- the artificial +world lives and breathes at its own cadence. As world builders, we must invent +time and craft the gears that drive our game's great clock. + +大多数游戏世界都有的特性是*时间*——虚构世界以其自己的昼夜生活呼吸。作为世界的架构这,我们必须发明时间,制造推动我们游戏时间运作的齿轮。 + +The patterns in this section are tools for doing just that. A [Game +Loop](game-loop.html) is the central axle that the clock spins on. Objects hear +its ticking through [Update Methods](update-method.html). We can hide the +computer's sequential nature behind a facade of snapshots of moments in time +using [Double Buffering](double-buffer.html) so that the world appears to +update simultaneously. + +这一部分的模式是建构这些方面的工具。一个[游戏循环](game-loop.html)是时钟的中间轴。对象通过[更新模式](update-method.html)来听到时钟的滴答声。我们可以用[双重缓存](double-buffer.html)存储快照来隐藏计算机的顺序执行,这样世界看起来是同步运行的。 + +## The Patterns + +* [Double Buffer](double-buffer.html) +* [Game Loop](game-loop.html) +* [Update Method](update-method.html) diff --git a/book/service-locator.markdown b/book/service-locator.markdown new file mode 100644 index 0000000..7ed721c --- /dev/null +++ b/book/service-locator.markdown @@ -0,0 +1,807 @@ +^title Service Locator +^section Decoupling Patterns + +## Intent + +## 意图 + +*Provide a global point of access to a service without coupling users to the +concrete class that implements it.* + +*提供一个服务的全局接入点,而不必让玩家和实现它的具体类耦合。* + +## Motivation + +## 动机 + +Some objects or systems in a game tend to get around, visiting almost every +corner of the codebase. It's hard to find a part of the game that *won't* need a +memory allocator, logging, or random numbers at some point. +Systems like those can be thought of as *services* that need to be available to +the entire game. + +一些游戏中的对象或者系统趋向于到处访问,拜访程序库中机会每一个角落。很难找到游戏中的一部分永远*不*需要内存分配,记录日志,或者一个随机数字。像这样的东西可以被视为整个游戏都需要的*服务*。 + +For our example, we'll consider audio. It doesn't have quite the reach of +something lower-level like a memory allocator, but it still touches a bunch of +game systems. A falling rock hits the ground with a crash (physics). A sniper +NPC fires his rifle and a shot rings out (AI). The user selects a menu item with +a beep of confirmation (user interface). + +在我们的例子中,我们考虑音频。他不需要接触底层的东西比如内存分配,但是仍然要接触一大堆游戏系统。一个滚动的演示撞击地面(物理)。一个NPC狙击手开了一枪,射出来子弹(AI)。永辉选择菜单项需要响一声确认(用户界面)。 + +Each of these places will need to be able to call into the audio system with +something like one of these: + +这些中的每一处都需要调用音频系统用像下面的东西: + +^code 15 + +Either gets us where we're trying to go, but we stumbled into some sticky +coupling along the way. Every place in the game calling into our audio system +directly references the concrete `AudioSystem` class and the mechanism for +accessing it -- either as a static class or a singleton. + +每一种都能让我们获得我们想要的,但是我们绊倒在终于的一些技巧耦合上。每一个调用你音频系统的游戏部分直接引用 了具体的`AudioSystem`类,和获取它的机制——可以是一个静态类或者一个单例。 + +These call sites, of course, have to be coupled to *something* in order to make a +sound play, but letting them poke at the concrete audio implementation directly +is like giving a hundred strangers directions to your house just so they can +drop a letter on your doorstep. Not only is it a little bit *too* personal, it's +a real pain when you move and you have to tell each person the new directions. + +这些调用,当然,需要耦合到*一些东西*上来播放声音,但是让他们可以接触到具体的音频实现就好像给了一百个陌生人你家的方向只是为了让他们在你的门口放一封信。这不但有关于*隐私*,这在你搬家然后需要告诉每个人你的新地址时更加痛苦。 + +There's a better solution: a phone book. People that need to get in touch with +us can look us up by name and get our current address. When we move, we tell the +phone company. They update the book, and everyone gets the new address. In fact, +we don't even need to give out our real address at all. We can list a P.O. box +or some other "representation" of ourselves instead. By having callers go +through the book to find us, we have *a convenient single place where we control +how we're found.* + +这里有一个更好的解决办法:一本电话薄。需要接触到我们的人们可以在上面查找并找到我们现在的地址。但我们搬家时,我们告诉电话公司。他们更新电话薄,每一个人都知道了新地址。事实上,我们甚至无需给出真实的地址。我们可以列一个转发信箱或者其他“代表”我们自己的东西。通过让调用者查询电话薄找到我们,我们需要一个*方便的地方控制我们可以被找到的地方*。 + +This is the Service Locator pattern in a nutshell -- it decouples code that needs +a service from both *who* it is (the concrete implementation type) and *where* +it is (how we get to the instance of it). + +这就是果壳中的服务定位模式——它解耦了需要服务的代码和服务由*谁*提供(那个具体的实现类)以及服务在*哪里*(我们如何获得它的实例)。 + +## The Pattern + +## 模式 + +A **service** class defines an abstract interface to a set of operations. A +concrete **service provider** implements this interface. A separate **service +locator** provides access to the service by finding an appropriate provider +while hiding both the provider's concrete type and the process used to locate +it. + +一个定义了有一堆操作的抽象接口**服务**类。一个具体的**服务提供者**实现这个接口。一个分离的**服务定位器**提供了通过查询合适的提供者接触服务的方法,同时隐藏了提供者的具体细节和需要定位它的进程。 + +## When to Use It + +## 何时使用 + +Anytime you make something accessible to every part of your program, +you're asking for trouble. That's the main problem with the Singleton pattern, and this +pattern is no different. My simplest advice for when to use a service locator +is: *sparingly*. + +任何时候你需要一个能够在程序的每一处被访问到的东西,你就是在找麻烦。这是单例模式的主要问题,这个模式也没有不同。我对何时使用服务定位的最简单的建议是:*少用*。 + +Instead of using a global mechanism to give some code access to an object it +needs, first consider *passing the object to it instead*. That's dead simple, and +it makes the coupling completely obvious. That will cover most of your needs. + +不要使用一个全局机制让某些代码接触他需要的代码,首先考虑*将对象传给它*。这超简单,也明显保持了解耦。这会覆盖你大部分的需求。 + +*But...* there are some times when manually passing around an object is +gratuitous or actively makes code harder to read. Some systems, like logging or +memory management, shouldn't be part of a module's public API. The parameters to +your rendering code should have to do with *rendering*, not stuff like logging. + +*但是……*这里有时候手动传送对象是不可能或者会让代码难以阅读。游戏系统,比如日志或内存管理,不该是模块公开API的一部分。传给你渲染代码的参数应该做与*渲染*相关,而不是和日志之类的。 + +Likewise, other systems represent facilities that are fundamentally singular in +nature. Your game probably only has one audio device or display system that it +can talk to. It is an ambient property of the environment, so plumbing it +through ten layers of methods just so one deeply nested call can get to it is +adding needless complexity to your code. + +同样,其他代表了设施的系统通常是只有一个的。你的游戏坑只有一个你可以交流的音频设备或者显示设备。这是周围环境的属性,所以加入一个穿过十层方法的负载调用会为你的代码证件不必要的复杂度。 + +In those kinds of cases, this pattern can help. As we'll see, it functions as a +more flexible, more configurable cousin of the Singleton pattern. When used well, it can make your codebase more flexible with little +runtime cost. + +这这些例子中,这个模式可以帮忙。就像我们将看到的那样,作为单例模式更加灵活,更加可配置的子孙,它能让你以很小的运行时代价获得更多的灵活性。 + + + +## Keep in Mind + +## 记住 + +The core difficulty with a service locator is that it takes a dependency -- a +bit of coupling between two pieces of code -- and defers wiring it up until +runtime. This gives you flexibility, but the price you pay is that it's harder +to understand what your dependencies are by reading the code. + +使用服务定位的核心难点是它需要从属——一点在两块代码之间的耦合——推迟到运行时再连线。这给了你更多的灵活度,但是你付出的代价是更难在阅读代码时理解你依赖的是什么。 + +### The service actually has to be located + +### 服务必须真的可定位 + +With a singleton or a static class, there's no chance for the instance we need +to *not* be available. Calling code can take for granted that it's there. But +since this pattern has to *locate* the service, we may need to handle cases +where that fails. Fortunately, we'll cover a strategy later to address this and +guarantee that we'll always get *some* service when you need it. + +通过使用单例或者静态类,我们需要的实例不可能*不*可用。调用代码保证了它就在那里。但是由于这个模式*定位*了服务,我们也许要处理失败的情况。幸运的是,我们会在之后介绍一种策略来处理它,保证我们总是在需要时能获得*一些*服务。 + +### The service doesn't know who is locating it + +### 服务不知道谁在定位它 + +Since the locator is globally accessible, any code in the game could be +requesting a service and then poking at it. This means that the service must be able +to work correctly in any circumstance. For example, a class that expects to +be used only during the simulation portion of the game loop and not during rendering +may not work as a service -- it wouldn't be able to ensure that it's being used +at the right time. So, if a class expects to be used only in a certain context, +it's safest to avoid exposing it to the entire world with this pattern. + +由于定位器是全局可获得的,任何游戏中的代码都可以请求服务然后接触到它。这就意味着服务必须在任何环境下正确工作。举个例子,一个类只能在游戏循环的模拟部分使用而不能子啊渲染部分使用也许不能作为服务——不能保证在正确的时间使用它。所以,如果你个类期望只在特定上下文中使用,更安全的是不要用这个模式将它暴露给整个世界。 + +## Sample Code + +## 示例代码 + +Getting back to our audio system problem, let's address it by exposing the +system to the rest of the codebase through a service locator. + +重回我们的音频问题,让我们将代码通过一个服务定位器暴露给代码库的剩余部分来解决这个问题。 + +### The service + +### 服务 + +We'll start off with the audio API. This is the interface that our service will +be exposing: + +我们从音频API开始。这是我们服务要暴露的接口: + +^code 9 + +A real audio engine would be much more complex than this, of course, but this +shows the basic idea. What's important is that it's an abstract interface class +with no implementation bound to it. + +一个真实的音频引擎比这复杂的多,当然,但是这展示了基本的理念。重要的是他是一个没有实现绑定的抽象接口类。 + +### The service provider + +### 服务提供者 + +By itself, our audio interface isn't very useful. We need a concrete +implementation. This book isn't about how to write audio code for a game +console, so you'll have to imagine there's some actual code in the bodies of +these functions, but you get the idea: + +只有它自己,我们的音频接口不是很有用。我们需要一个具体实现。这本书不是关于如何为游戏主机写音频代码,所以你的想象这些函数中有实际的代码,但是你能够知道意思: + +^code 10 + +Now we have an interface and an implementation. The remaining piece is the +service locator -- the class that ties the two together. + +现在我们有一个接口和实现了。剩下的部分是服务定位器——那个将两者绑在一起的类 + +### A simple locator + +### 一个简单的定位器 + +The implementation here is about the simplest kind of service locator you can +define: + +这里的实现是你可以定义的最简单的服务定位器: + + + +^code 1 + + + +The static `getAudio()` function does the locating. We can call it from +anywhere in the codebase, and it will give us back an instance of our `Audio` +service to use: + +这个静态的`getAudio()`函数完成了定位。我们可以从代码库的任何地方调用它,它歌会给我们一个`Audio`服务使用: + +^code 5 + +The way it "locates" is very simple -- it relies on some outside code to register +a service provider before anything tries to use the service. When the game is +starting up, it calls some code like this: + +它“定位”的方式十分简单——它依赖外部的代码在有人使用服务前注册一个服务提供者。但有些开始时,它调用一些这样的代码: + +^code 11 + +The key part to notice here is that the code that calls `playSound()` isn't aware of +the concrete `ConsoleAudio` class; it only knows the abstract `Audio` interface. Equally +important, not even the *locator* class is coupled to the concrete service +provider. The *only* place in code that knows about the actual concrete class is +the initialization code that provides the service. + +这里需要注意的关键部分是调用`playSound()`的代码没有意识到任何具体的`ConsoleAudio`类;它只知道抽象的`Audio`接口。同样重要的,*定位器*类都没有与具体的服务提供者耦合。代码中*唯一*知道哪个具体类提供了服务的地方是初始化代码。 + +There's one more level of decoupling here: the `Audio` interface isn't aware of +the fact that it's being accessed in most places through a service locator. As +far as it knows, it's just a regular abstract base class. This is useful because +it means we can apply this pattern to *existing* classes that weren't +necessarily designed around it. This is in contrast with Singleton, which affects the design of the "service" +class itself. + +这里有更高层次的解耦:`Audio`接口没有意识到它通过一个服务定位器来被访问。据他所知,它只是一个常见的抽象基类。这是很有用的,因为这意味着我们可以将这个模式应用到*现有的*而不一定围绕其设计的类上。这与单例形成了对比,那个会影响“服务”类本身的设计。 + +### A null service + +### 一个空服务 + +Our implementation so far is certainly simple, and it's pretty flexible too. But +it has one big shortcoming: if we try to use the service before a provider has been registered, it returns `NULL`. +If the calling code doesn't check that, we're going to crash the game. + +我们现在的实现很简单,而且也很灵活。但是它有一个巨大的短处:如果我们想要在提供者注册前使用服务,它会返回`NULL`。如果调用代码没有坚持那个,我们就让游戏崩溃了。 + + + +Fortunately, there's another design pattern called "Null Object" that we can use +to address this. The basic idea is that in places where we would return `NULL` +when we fail to find or create an object, we instead return a special object +that implements the same interface as the desired object. Its implementation +basically does nothing, but it allows code that receives the object to safely +continue on as if it had received a "real" one. + +幸运的是,还有一种设计模式叫做“空对象”,我们可以用它处理这个。这里的基本注意就是在我们没能找到或者没有以让程序能正确运行的顺序调用时,我们不返回`NULL`,我们返回一个特定的对象实现了需要对象一样的接口。它的实现简单的什么也不做,但是它运行代码接受对象,保证安全运行,好像它收到了一个“真的”。 + +To use this, we'll define another "null" service provider: + +为了使用它,我们定义另一个“空”服务提供者: + +^code 7 + +As you can see, it implements the service interface, but doesn't actually do +anything. Now, we change our locator to this: + +就像我们看到的那样,它实现了服务接口,但是没有干任何实事。现在,我们将我们的定位器改成这样: + + + +^code 8 + + + +Calling code will never know that a "real" service wasn't found, nor does it +have to worry about handling `NULL`. It's guaranteed to always get back a valid +object. + +调用代码永远不知道一个“真正的”服务没有找到,或者不必担心处理`NULL`。这保证了它永远获得一个有效的对象。 + +This is also useful for *intentionally* failing to find services. If we want to +disable a system temporarily, we now have an easy +way to do so: simply don't register a provider for the service, and the locator +will default to a null provider. + +这对*故意*找不到服务也很有用。如果我们想暂时停用一个系统,我们现在有更简单的方式来做了:简单的不要为服务注册提供者,定位器会默认提供一个空提供者。 + + + +### Logging decorator + +### 日志装饰器 + +Now that our system is pretty robust, let's discuss another refinement this +pattern lets us do -- decorated services. I'll explain with an example. + +现在我们的系统非常强健了,让我们讨论这个模式允许的另一个好处——装饰服务。我会用一个例子解释。 + +During development, a little logging when interesting events occur can help you +figure out what's going on under the hood of your game engine. If you're working +on AI, you'd like to know when an entity changes AI states. If you're the sound +programmer, you may want a record of every sound as it plays so you can check +that they trigger in the right order. + +在开发过程中,一个记录有趣事情发生的小小日志系统可以帮助你查出你游戏引擎正在处于何种状态。如果你在AI上工作,你想要知道哪个实体改变了AI状态。如果你是音频程序员,你也许想记录每一个播放的声音这样你可以检查他们是否以正确的顺序触发。 + +The typical solution is to litter the code with calls to some `log()` +function. Unfortunately, that replaces one problem with another -- now we have +*too much* logging. The AI coder doesn't care when sounds are playing, +and the sound person doesn't care about AI state transitions, but now they both +have to wade through each other's messages. + +通常的解决方案是向代码中丢一些对`log()`函数的调用。不幸的是,这用一个问题代替了另一个——现在我们有*太多*日志。AI程序员不关心什么时候声音在播放,声音程序员也不在乎AI状态转换,但是他们现在都得在对方的消息中跋涉。 + +Ideally, we would be able to selectively enable logging for just the stuff we +care about, and in the final game build, there'd be no logging at all. If the +different systems we want to conditionally log are exposed as services, then we +can solve this using the Decorator pattern. Let's +define another audio service provider implementation like this: + +理念上,我们应该可以选择性的为我们关心的事物启动日志,在最终完成的游戏中,不应该有日志。如果我们想要有条件的为不同的系统记录日志被改写为服务,那么我们就可以用装饰器模式。让我们顶一个另一个音频服务提供者实现如下: + +^code 12 + +As you can see, it wraps another audio provider and exposes the same interface. +It forwards the actual audio behavior to the inner provider, but it also logs each +sound call. If a programmer wants to enable audio logging, they call this: + +就像你看到的那样,它包裹了整个音频提供者保留了同样的接口。它将世界的音频行为转发给内部的提供者,但它也同时记录每一个音频调用。如果一个程序员需要启动音频日志,他们调用这个: + +^code 13 + +Now, any calls to the audio service will be logged before continuing as before. +And, of course, this plays nicely with our null service, so you can both +*disable* audio and yet still log the sounds that it *would* play if sound were +enabled. + +现在,任何对音频服务的调用在回到从前之前都会继续下去。并且,当然,它和我们的空服务也很好的相处,所以你能*启用*音频也能继续记录如果声音被启用时*将会*播放的声音。 + +## Design Decisions + +## 设计决策 + +We've covered a typical implementation, but there are a couple of ways that it can +vary based on differing answers to a few core questions: + +我们包括了一种典型的实现,但是这里还有很多方式可以实现,基于一些对核心问题的回答: + +### How is the service located? + +### 服务是如何被定位的? + + * **Outside code registers it:** + + * **外部代码注册它:** + + This is the mechanism our sample code uses to locate the service, and it's the + most common design I see in games: + + 这是我们样例代码中使用的机制来定位服务,这也是我在游戏中最常见的设计方式: + + * *It's fast and simple.* The `getAudio()` function simply returns a + pointer. It will often get inlined by the compiler, so we get a nice + abstraction layer at almost no performance cost. + + * *简单快捷。*`getAudio()`函数简单的返回一个指针。这通常被编译器内联了,所以我们几乎没有付出性能损失就获得了一个很好的抽象层。 + + * *We control how the provider is constructed.* Consider a service for + accessing the game's controllers. We have two concrete providers: one + for regular games and one for playing online. The online provider passes + controller input over the network so that, to the rest of the game, + remote players appear to be using local controllers. + + * *我们可以控制提供者是如何被构建的。*想想一个接触游戏控制器的服务。我们使用两个具体的提供者:一个是给常规游戏另一个给在线游戏。在线的那个跨越网络提供控制器的输入,这样,对于游戏的其他部分,远程玩家好像在使用中本地控制器。 + + To make this work, the online concrete provider needs to know the IP + address of the other remote player. If the locator itself was + constructing the object, how would it know what to pass in? The `Locator` + class doesn't know anything about online at all, much less some other + user's IP address. + + 为了让这个工作,在想的具体提供者需要知道其他远程玩家的IP。如果定位器本身构建对象,它怎么知道传进来什么?`Locator`类不知道任何有关在线的东西,就更不用说其他用户的IP地址了。 + + Externally registered providers dodge the problem. Instead of the + locator constructing the class, the game's networking code instantiates + the online-specific service provider, passing in the IP address it + needs. Then it gives that to the locator, who knows only about the + service's abstract interface. + + 外部注册的提供者闪避了这个问题。不是定位器构造类,游戏的网络代码实例化特定的在线特定服务提供器,传给它需要的IP地址。然后他把服务提供给定位器,定位器只知道服务的抽象接口。 + + * *We can change the service while the game is running.* We may not use + this in the final game, but it's a neat trick during development. While + testing, we can swap out, for example, the audio service with the null + service we talked about earlier to temporarily disable sound while the + game is still running. + + * *我们可以在游戏运行改变服务。*我们也许不会在最终的游戏版本中使用这个,但是这是一个子啊开发过程中有效的技巧。但测试时,我们可以切换,举个例子,音频服务为早先提到的空服务来临时的关闭声音,即使游戏正在运行。 + + * *The locator depends on outside code.* This is the downside. Any code + accessing the service presumes that some code somewhere has already + registered it. If that initialization doesn't happen, we'll either crash + or have a service mysteriously not working. + + * *定位器依赖外部代码。*这就是缺点。任何接触服务的代码假定某处的代码已经注册过了。如果初始化没有做,我们要么会崩溃,要么有一个服务迷之不工作。 + + * **Bind to it at compile time:** + + * **在编译时间绑定:** + + The idea here is that the "location" process actually occurs at compile time + using preprocessor macros. Like so: + + 这里的主意是使用预处理器,在编译时间处理“位置”。就像这样: + + ^code 2 + + Locating the service like this implies a few things: + + 像这样定位服务暗示了一些事情: + + * *It's fast.* Since all of the real work is done at compile time, there's + nothing left to do at runtime. The compiler will likely inline the + `getAudio()` call, giving us a solution that's as fast as we could hope + for. + + * *快速。*由于所有的工作都在编译时做完了,没有东西需要在运行时做了。编译器很可能会内联`getAudio()`调用,给我们一个我们希望的尽可能快的解决方案。 + + * *You can guarantee the service is available.* Since the locator owns the + service now and selects it at compile time, we can be assured that if + the game compiles, we won't have to worry about the service being + unavailable. + + * *你可以保证服务是可用的。*由于定位器现在拥有服务,在编译时就选择了,我们可以保证入股游戏编译完成,我们不必再担心服务不可用。 + + * *You can't change the service easily.* This is the major downside. Since + the binding happens at build time, anytime you want to change the + service, you've got to recompile and restart the game. + + * *你无法轻易改变服务。*这是主要的缺点。由于绑定发生在编译时间,任何时候你想要改变服务,你都得重新编译游戏并重启游戏。 + + * **Configure it at runtime:** + + * **在运行时设置:** + + Over in the khaki-clad land of enterprise business software, if you say + "service locator", this is what they'll have in mind. When the service is + requested, the locator does some magic at + runtime to hunt down the actual implementation requested. + + 在卡其色衣物覆盖的事业商业单位,乳沟你说“服务定位器”,这就是他们脑中的东西。的那个服务被请求时,定位器在运行时做一些魔法般的事情来追踪请求的真实实现。 + + + + Typically, this means loading a configuration file that identifies the + provider and then using reflection to instantiate that class at runtime. This + does a few things for us: + + 典型的,这意味着加载一个设置文件,确认提供者,然后使用一些反射说明运行中的类。这为我们做了一些事情: + + * *We can swap out the service without recompiling.* This is a little more + flexible than a compile-time-bound service, but not quite as flexible as + a registered one where you can actually change the service while the + game is running. + + * *我们可以换出服务而无需重新编译。*这比编译时绑定多了一个小小的灵活性,但是不想注册的那样灵活,那里你可以真正的在运行游戏的时候改变服务。 + + * *Non-programmers can change the service.* This is nice for when the + designers want to be able to turn certain game features on and off but + aren't comfortable mucking through source code. (Or, more likely, the + *coders* aren't comfortable with them mucking through it.) + + * *非程序员也可改变服务。*这对于想要在开关特定游戏特性的设计者来说是很好的,但清理源代码就不怎么舒服了。(或者,更可能的,*编程者*在设计者清理的时候就不舒服。) + + * *The same codebase can support multiple configurations simultaneously.* + Since the location process has been moved out of the codebase entirely, + we can use the same code to support multiple service configurations + simultaneously. + + * *同样的代码库可以同时支持多种设置。*由于定位处理被从代码库中晚期的移出,我们可以使用相同的代码来之处同时支持多种服务设置。 + + This is one of the reasons this model is appealing over in enterprise + web-land: you can deploy a single app that works on different server + setups just by changing some configs. Historically, this was less + useful in games since console hardware is pretty well-standardized, but + as more games target a heaping hodgepodge of mobile devices, this is + becoming more relevant. + + 这就是这个模型在企业网站上广泛应用的原因之一:你可以在不同的服务器上发布相同的应用,只需要修改一些设置。历史上看来,这在游戏中没什么用,因为主机硬件本身是好好标准化了的,但是对于很多目标是大杂烩的移动设备,这顶就很有关系了。 + + * *It's complex.* Unlike the previous solutions, this one is pretty + heavyweight. You have to create some configuration system, possibly + write code to load and parse a file, and generally *do some stuff* to + locate the service. Time spent writing this code is time not spent on + other game features. + + * *复杂。*不想前面的解决方案,这一个是重量级的。你的创建一些设置系统,也许要写代码来加载和粘贴文件,通常*要做些事情*来定位服务。花费字写这些代码上的时间就是没有花费在写其他游戏特性的时间。 + + * *Locating the service takes time.* And now the smiles really turn to + frowns. Going with runtime configuration means you're burning some CPU + cycles locating the service. Caching can minimize this, but that still + implies that the first time you use the service, the game's got to go + off and spend some time hunting it down. Game developers *hate* burning + CPU cycles on something that doesn't improve the player's game + experience. + + * *加载服务需要时间。*现在笑脸要变成皱眉脸了。子啊运行时设置意味着你在消耗CPU循环加载服务。缓存可以最小化这个,但是仍然暗示在你首次使用服务时,游戏需要暂停等点时间追上它。游戏开发者*讨厌*消耗CPU循环在不能提高游戏体验的地方。 + +### What happens if the service can't be located? + +### 如果服务不能被定位怎么办? + + * **Let the user handle it:** + + * **让玩家处理它:** + + The simplest solution is to pass the buck. If the locator can't find the + service, it just returns `NULL`. This implies: + + 最简单的解决方案就是把责任退回去。如果定位器不能找到服务,它只需返回`NULL`。这个暗示着: + + * *It lets users determine how to handle failure.* Some users may consider + failing to find a service a critical error that should halt the game. + Others may be able to safely ignore it and continue. If the locator + can't define a blanket policy that's correct for all cases, then passing + the failure down the line lets each call site decide for itself what the + right response is. + + * *它让用户决定如何掌控失败。*一些用户也许在收到找不到服务的关键错误时应该暂停游戏。其他的可能会安全的忽视然后继续。如果定位器不能定义一个完全的政策对所有的情况都正确,那么就将失败传递下去,让每一个调用它的站点决定什么事正确的回应。 + + * *Users of the service must handle the failure.* Of course, the corollary + to this is that each call site *must* check for failure to find the + service. If almost all of them handle failure the same way, that's a lot + duplicate code spread throughout the codebase. If just one of the + potentially hundreds of places that use the service fails to make that + check, our game is going to crash. + + * *使用服务的用户必须处理失败。*当然,这个的必然结果就是每一个调用站点都*必须*检查寻找服务的失败。如果他们都是以相同方式来处理的,这就在代码库中有很多重复的代码。如果一百个中有一个忘了检查,我们的游戏就会崩溃。 + + * **Halt the game:** + + * **挂起游戏:** + + I said that we can't *prove* that the service will always be available at + compile-time, but that doesn't mean we can't *declare* that availability is + part of the runtime contract of the locator. The simplest way to do this is + with an assertion: + + 我说过我们不能*保证*服务在编译时总是可用的,但是不意味着我们不能*声明*可用性是游戏定位器运行合约的一部分。做这点最简答的方法就是使用一个断言: + + ^code 4 + + If the service isn't located, the game stops before any subsequent code + tries to use it. The `assert()` call there doesn't solve the problem of + failing to locate the service, but it does make it clear whose problem it + is. By asserting here, we say, "Failing to locate a service is a bug in the + locator." + + 如果服务没有被找到,游戏停在试图使用它的后续代码之前。这里的`assert()`没有解决无法定位服务的问题,但是它确实明确了问题是什么。通过这里的断言,我们说,“无法定位一个服务是定位器的漏洞。” + + + + So what does this do for us? + + 那么这件事为我们做了什么呢? + + * *Users don't need to handle a missing service.* Since a single service + may be used in hundreds of places, this can be a significant code + saving. By declaring it the locator's job to always provide a service, + we spare the users of the service from having to pick up that slack. + + * *用户不必需要处理一个失踪的服务。*由于一个简单的服务也许子啊成百上千的地方被使用,这是一个很大的代码节约。通过说明总能提供一个服务它是定位器的工作,我们节约了服务的用户处理这个松懈的精力。 + + * *The game is going to halt if the service can't be found.* On the off + chance that a service really can't be found, the game is going to halt. + This is good in that it forces us to address the bug that's preventing + the service from being located (likely some initialization code isn't + being called when it should), but it's a real drag for everyone else + who's blocked until it's fixed. With a large dev team, you can incur + some painful programmer downtime when something like this breaks. + + * *如果服务没有找到,游戏会挂起。*在极小的可能性下,一个服务真的找不到,游戏就会挂起。强迫我们解决定位服务的漏洞是很好的(比如一些本该调用的初始化代码没有被调用),但对其他所有被阻塞的人都会拖延到被修复。和一个大的开发团队工作,当这种事情发生时,你会增加一些痛苦的编程时间。 + + * **Return a null service:** + + * **返回一个空服务:** + + We showed this refinement in our sample implementation. Using this means: + + 我们在我们的样例实现中展示了这种修复。使用它意味着: + + * *Users don't need to handle a missing service.* Just like the previous + option, we ensure that a valid service object will always be returned, + simplifying code that uses the service. + + * *用户不必处理一个丢失的服务。*就像前面的选项一样,我们保证了一个可用的服务总是会被返回,简化了使用服务的代码。 + + * *The game will continue if the service isn't available.* This is both a + boon and a curse. It's helpful in that it lets us keep running the game + even when a service isn't there. This can be really helpful on a large + team when a feature we're working on may be dependent on some other + system that isn't in place yet. + + * *如果服务不可用,游戏仍将继续。*这既有好处又有坏处。对于让我们在没有服务的情况下依然运行游戏是很有用的。在大的团队中,当我们工作依赖的其他特性或者依赖的其他系统还没有就位时这也是很有用的。 + + The downside is that it may be harder to debug an *unintentionally* + missing service. Say the game uses a service to access some data and + then make a decision based on it. If we've failed to register the real + service and that code gets a null service instead, the game may not + behave how we want. It will take some work to + trace that issue back to the fact that a service wasn't there when we + thought it would be. + + 缺点在于,较难为一个*无意*缺失的设备查找漏洞。假设游戏用一个服务去获取数据,然后基于数据做出决策。如果我们无法注册真正的服务,代码获得了一个空服务,游戏也许不会像2我们期望的那样行动。这会花一些时间才能追踪到这个问题,发现我们认为有的服务是缺失的。 + + + +Among these options, the one I see used most frequently is simply asserting that +the service will be found. By the time a game gets out the door, it's been very +heavily tested, and it will likely be run on a reliable piece of hardware. The +chances of a service failing to be found by then are pretty slim. + +在这些选项中,我看到最常使用的是简单的断言服务会被找到。在游戏发布的时候,它经历了严重的测试,它会在可信赖的硬件上运行。服务无法找到的机会非常渺茫。 + +On a larger team, I encourage you to throw a null service in. It doesn't take +much effort to implement, and can spare you from some downtime during +development when a service isn't available. It also gives you an easy way to +turn off a service if it's buggy or is just distracting you from what you're +working on. + +在更大的团队中,我鼓励你使用一个空服务。这不会花太多时间实现,可以减少开发中一个服务不可用的缺陷。这也给你了一个简单的方式去关闭一个服务,无论他是有漏洞还是干扰到了你真正工作的东西。 + +### What is the scope of the service? + +### 服务的服务范围有多大? + +Up to this point, we've assumed that the locator will provide access to the +service to *anyone* who wants it. While this is the typical way the pattern is +used, another option is to limit access to a single class and its descendants, +like so: + +到了现在,我们假设定位器会提供服务给*任何*需要它的人。当这是这个模式通常的使用方式,另一个选项是局限到一个简单的类和他的子类,就像这样: + +^code 3 + +With this, access to the service is restricted to classes that inherit `Base`. +There are advantages either way: + +通过这些,对服务的接触被收缩到了继承`Base`的类。这两种各有千秋: + + * **If access is global:** + + * **如果全局可获取:** + + * *It encourages the entire codebase to all use the same service.* Most + services are intended to be singular. By allowing the entire codebase to + have access to the same service, we can avoid random places in code + instantiating their own providers because they can't get to the "real" + one. + + * *他鼓励整个代码库使用同样的服务。*大多数服务都被设计成单一的。通过允许整个代码库接触到相同的服务,我们可以避免以无法获得“真正的”提供者而实例化他们之间的提供者。 + + * *We lose control over where and when the service is used.* This is the + obvious cost of making something global -- anything can get to it. The Singleton chapter has a + full cast of characters for the horror show that global scope can spawn. + + * *我们失去了何时何地服务被使用的控制权。*这是让某物全局化的明显代价——任何东西都能接触它。单例模式一章讲了全局变量可以多么糟糕的拖慢系统。 + + * **If access is restricted to a class:** + + * **如果接触被限制字一个类当中:** + + * *We control coupling.* This is the main advantage. By limiting a service + to a branch of the inheritance tree, we can make sure systems that + should be decoupled stay decoupled. + + * *我们控制耦合。*这是主要的优点。通过显式服务给一棵继承树的分支,我们保证了应该解耦的系统保持解耦。 + + * *It can lead to duplicate effort.* The potential downside is that if a + couple of unrelated classes *do* need access to the service, they'll + each need to have their own reference to it. Whatever process is used to + locate or register the service will have to be duplicated between those + classes. + + * *这可能导致重复的付出。*潜在的缺点是如果一对无关的类*确实*需要接触服务,他们每一个都要拥有它的应用。无论是谁定位或者注册服务,他也需要在这些类之间复制。 + + (The other option is to change the class hierarchy around to give those + classes a common base class, but that's probably more trouble than it's + worth.) + + (另一个选项是改变类的继承趁此,给这些类一个公共的基类,但这也许引起的麻烦多于收益。) + +My general guideline is that if the service is restricted to a single domain in +the game, then limit its scope to a class. For example, a service for getting +access to the network can probably be limited to online classes. Services that +get used more widely like logging should be global. + +我的通用准则是,如果服务局限在游戏的一个领域中,那么限制他的工作范围在一个类上面。举个例子,一个获取网络接口的服务可能限制在在线类中。服务像日志这样更加广泛应用的应该是全局的。 + +## See Also + +## 参见 + + * The Service Locator pattern is a sibling to Singleton in many ways, so it's worth looking at + both to see which is most appropriate for your needs. + + * 服务定位模式在很多方面是单例模式的兄弟,在应用前看看两者哪一个更适合你的需求是很值得的。 + + * The [Unity](http://unity3d.com) framework uses this pattern in concert with + the Component pattern in its + [`GetComponent()`][get-component] method. + + * [Unity](http://unity3d.com)框架在它的[`GetComponent()`][get-component]方法中使用这个模式协调它的组件模式 + + * Microsoft's [XNA][] framework for game development has this pattern built + into its core `Game` class. Each instance has a `GameServices` object that + can be used to register and locate services of any type. + + * 微软的[XNA][]游戏开发框架子啊它的核心`Game`类中内建了这种模式。每一个实体都有一个`GameServices`对象可以用来注册和定位任何种类的服务。 + +[get-component]: http://docs.unity3d.com/412/Documentation/ScriptReference/Component.GetComponent.html?from=index +[xna]: http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.game.services.aspx diff --git a/book/singleton.markdown b/book/singleton.markdown new file mode 100644 index 0000000..9c3c31a --- /dev/null +++ b/book/singleton.markdown @@ -0,0 +1,737 @@ +^title Singleton +^section Design Patterns Revisited + +This chapter is an anomaly. Every other chapter in this book shows +you how to use a design pattern. This chapter shows you how *not* to use +one. + +这个章节不同寻常。这本书的其他章节展示如何使用一个设计模式。这个章节向你展示如何*不*使用一个设计模式。 + +Despite noble intentions, the Singleton pattern described +by the Gang of Four usually does more harm than good. They stress that the +pattern should be used sparingly, but that message was often lost in translation +to the game industry. + +不管他高贵的意图,GoF描述的单例模式通常带来的坏处多于好处。他们强调这个模式应该谨慎使用,但是这个消息在传到游戏工业界时经常就消失了。 + +Like any pattern, using Singleton where it doesn't belong is about as helpful as +treating a bullet wound with a splint. Since it's so overused, most of this +chapter will be about *avoiding* singletons, but first, let's go over the +pattern itself. + +就像其他模式一样,在不合适的地方使用单例模式就好像用夹板处理子弹伤口。由于它是如此的滥用,这章的大部分都在讲如何*回避*单例模式,但首先,我们浏览一遍模式本身。 + + + +## The Singleton Pattern + +## 单例模式 + +*Design Patterns* summarizes Singleton like this: + +*设计模式*像这样总结单例模式: + +> Ensure a class has one instance, and provide a global point of access to it. + +> 保证一个类只有一个实例,并提供一个全局接入点来获得它。 + +We'll split that at "and" and consider each half separately. + +我们从“并”分割这个句子,分别考虑每一部分。 + +### Restricting a class to one instance + +### 限制一个类只有一个实例 + +There are times when a class cannot perform correctly if there is more than one +instance of it. The common case is when the class interacts with an external +system that maintains its own global state. + +有时候,如果有超过一个实例,类不能正确的运行。通常情况是类与保持了它自己的全局状态的外部系统互动。 + +Consider a class that wraps an underlying file system API. Because file +operations can take a while to complete, our class performs operations +asynchronously. This means multiple operations can be running concurrently, so +they must be coordinated with each other. If we start one call to create a file +and another one to delete that same file, our wrapper needs to be aware of both +to make sure they don't interfere with each other. + +考虑一个包裹了潜在文件系统的API。因为文件操作需要一段时间完成,我们的类异步完成操作。这就意味着多个操作可以同时运行,所以他们必须相互协调。乳沟从吗一个调用创建文件,另一个删除同一文件,我们的包装器需要同时考虑这两个来保证他们没有相互妨碍。 + +To do this, a call into our wrapper needs to have access to every previous +operation. If users could freely create instances of our class, one instance +would have no way of knowing about operations that other instances started. +Enter the singleton. It provides a way for a class to ensure at compile time +that there is only a single instance of the class. + +为了做到这一点,一个对包装器的调用需要接触之前的每一个操作。如果用户可以自由的创建类的实例,一个实例无法知道另一个实例开始的操作。进入单例模式。它提供了一种方式构建类,在编译时保证类只有单一实例。 + +### Providing a global point of access + +### 提供一个全局接入点 + +Several different systems in the game will use our file system wrapper: logging, +content loading, game state saving, etc. If those systems can't create their own +instances of our file system wrapper, how can they get ahold of one? + +游戏中的不同系统都会使用我们的文件系统包装类:日志,内容加载,游戏状态保存,等等。如果这些系统不能创建他们自己的文件系统包装实例,他们如何获取一个呢? + +Singleton provides a solution to this too. In addition to creating the single +instance, it also provides a globally available method to get it. This way, +anyone anywhere can get their paws on our blessed instance. All together, the +classic implementation looks like this: + +单例模式为这个也提供了一个解决方案。处理创建单一实例,它也提供了一种全局可用方法来获得它。这种范式,无论何处何人都可以抓取我们受祝福的实例。所有的加起来,类独栋经典实现如下: + +^code 1 + +The static `instance_` member holds an instance of the class, and the private +constructor ensures that it is the *only* one. The public static `instance()` +method grants access to the instance from anywhere in the codebase. It is also +responsible for instantiating the singleton instance lazily the first time +someone asks for it. + +静态的`instance_`成员保持了一个类的实例,私有的构造器保证了*只有*一个。公开的静态方法`instance()`让代码库的任何地方都能获取实例。他同样负责在第一个有人请求的时候才懒惰地实例化单例实例。 + +A modern take looks like this: + +一个现代的实现看起来是这样的: + +^code local-static + +C++11 mandates that the initializer for a local +static variable is only run once, even in the presence of concurrency. So, +assuming you've got a modern C++ compiler, this code is thread-safe where the +first example is not. + +C++11保证了本地静态变量只会运行一次,哪怕是同时发生的情况下。因此,假设你有一个现代C++编译器,这个代码是线程安全的而第一个例子不是。 + + + +## Why We Use It + +## 为什么我们使用它 + +It seems we have a winner. Our file system wrapper is available wherever we need +it without the tedium of passing it around everywhere. The class itself cleverly +ensures we won't make a mess of things by instantiating a couple of instances. +It's got some other nice features too: + +这看起来已经决出了优胜者。我买单文件系统包装类在我们需要的地方到处可用,而无需沉闷的将它到处传递。类本身明确保证我们我们不会实例化多个实例。它还有很多其他的好性质: + +* **It doesn't create the instance if no one uses it.** Saving memory and CPU + cycles is always good. Since the singleton is initialized only when it's + first accessed, it won't be instantiated at all if the game never asks for + it. + +* **如果没人用,就不必创建实例。**节约内存和CPU循环总是好的。由于单例只在第一次被使用时实例化,如果游戏永远不请求,那么它不会被实例化。 + +* **It's initialized at runtime.** A common alternative to Singleton is a + class with static member variables. I like simple solutions, so I use static + classes instead of singletons when possible, but there's one limitation + static members have: automatic initialization. The compiler initializes + statics before `main()` is called. This means they can't use information + known only once the program is up and running (for example, configuration + loaded from a file). It also means they can't reliably depend on each other + -- the compiler does not guarantee the order in which statics are + initialized relative to each other. + +* **它在运行时实例化。**一个通常的单例模式选项是一个有着静态成员变量的类。我喜欢简单的解决方案,因此我尽可能使用静态类而不是单例,但是静态成员有一个限制:自动初始化。编译器在`main()`调用前初始化静态变量。这就意味着他们不能使用在程序加载并运行的信息(举个例子,从文件加载的配置)。这也意味着他们不能可靠地依赖彼此——编译器没有保证以什么样的顺序初始化静态变量。 + + Lazy initialization solves both of those problems. The singleton will be + initialized as late as possible, so by that time any information it needs + should be available. As long as they don't have circular dependencies, one + singleton can even refer to another when initializing itself. + + 惰性初始化解决了以上两个问题。单例会尽可能晚的初始化,所以那时所有他需要的信息都应该可用了。只要他们没有环状依赖,一个单例在初始化它自己的时候甚至可以引用另一个单例。 + +* **You can subclass the singleton.** This is a powerful but often overlooked + capability. Let's say we need our file system wrapper to be cross-platform. + To make this work, we want it to be an abstract interface for a file system + with subclasses that implement the interface for each platform. Here is the + base class: + +* **你可以将单例作为子类。**这是一个很有用但通常过分了的能力。假设我们需要我们的文件系统包装类跨平台。为了让这个工作,我们需要它作为每个文件系统抽象出来的接口,有子类为每个平台实现接口。这就是基类: + + ^code 2 + + Then we define derived classes for a couple of platforms: + + 然后我们为一堆平台定义推导类。 + + ^code derived-file-systems + + Next, we turn `FileSystem` into a singleton: + + 下一步,我们把`FileSystem`变成一个单例: + + ^code 3 + + The clever part is how the instance is created: + + 灵巧之处在于实例是如何被创建的。 + + ^code 4 + + With a simple compiler switch, we bind our file system wrapper to the + appropriate concrete type. Our entire codebase can access the file system + using `FileSystem::instance()` without being coupled to any + platform-specific code. That coupling is instead encapsulated within the + implementation file for the `FileSystem` class itself. + + 通过一个简单的编译器的switch,我们把我们的的文件系统包装类绑定到合适的具体类型上。我买单整个代码库都可以使用`FileSystem::instance()`接触到文件系统,而无需和任何平台特定的代码耦合。耦合发生在为特定平台写的`FileSystem`类实现文件中。 + +This takes us about as far as most of us go when it comes to solving a problem +like this. We've got a file system wrapper. It works reliably. It's available +globally so every place that needs it can get to it. It's time to check in the +code and celebrate with a tasty beverage. + +这带我们完成了解决这样一个问题我们中的大多数需要靠的的程度。我们有一个文件系统包装类。它可靠的工作。它全局有效这样只要需要就能获得。是时候签入代码然后用美味饮料庆祝了。 + +## Why We Regret Using It + +## 为什么我们不要使用它 + +In the short term, the Singleton pattern is relatively benign. Like many design +choices, we pay the cost in the long term. Once we've cast a few unnecessary +singletons into cold hard code, here's the trouble we've bought ourselves: + +短期来看,单例模式是相对良性的。就像其他设计选择一样,我们需要从长期考虑。一旦我们将一些不必要的单例写进代码,这里有我们给自己带来的麻烦: + +### It's a global variable + +### 它是一个全局变量 + +When games were still written by a couple of guys in a garage, pushing the +hardware was more important than ivory-tower software engineering principles. +Old-school C and assembly coders used globals and statics without any trouble +and shipped good games. As games got bigger and more complex, architecture and +maintainability started to become the bottleneck. We struggled to ship games not +because of hardware limitations, but because of *productivity* limitations. + +当游戏还是几个家伙在车库中完成的东西时,榨干硬件比象牙塔里的软件工程原则更加重要。老式的C和汇编程序员毫无问题的使用全局变量和静态变量,发布好游戏。随着游戏变得越来越大,越来越复杂,建构和管理开始变成瓶颈,我们挣扎着发布游戏不但因为硬件限制,而且因为*生产力*限制。 + +So we moved to languages like C++ and started applying some of the hard-earned +wisdom of our software engineer forebears. One lesson we learned is that global +variables are bad for a variety of reasons: + +所以我们迁移到了像C++这样的语言,开始将一些从我吗软件工程师祖先那里学到的智慧应用于实际。我们学到的一课就是全局变量因为多种原因而不好: + +* **They make it harder to reason about code.** Say we're tracking down a bug + in a function someone else wrote. If that function doesn't touch any global + state, we can wrap our heads around it just by understanding the body of the + function and the arguments being passed to it. + +* **他们让理解代码更加困难。**假设我们追踪一个其他人写的函数中的漏洞。如果函数没有碰到任何全局状态,我们可以让给脑子围着它转只需搞懂函数的主体和传给它的变量。 + + + + Now, imagine right in the middle of that function is a call to + `SomeClass::getSomeGlobalData()`. To figure out what's going on, we have + to hunt through the entire codebase to see what touches that global data. + You don't really hate global state until you've had to `grep` a million + lines of code at three in the morning trying to find the one errant call + that's setting a static variable to the wrong value. + + 现在考虑在函数的中间部分是一个对`SomeClass::getSomeGlobalData()`的调用。为了查明发生了什么,我们的追踪整个代码库来看看什么修改了全局数据。你真的不需要讨厌全局变量,在凌晨三点使用`grep`搜索数百万行代码来搞清楚哪一个错误滴啊用讲一个静态变量设为了错误的值。 + +* **They encourage coupling.** The new coder on your team isn't familiar with + your game's beautifully maintainable loosely coupled architecture, but he's + just been given his first task: make boulders play sounds when they crash + onto the ground. You and I know we don't want the physics code to be coupled + to *audio* of all things, but he's just trying to get his task done. + Unfortunately for us, the instance of our `AudioPlayer` is globally visible. + So, one little `#include` later, and our new guy has compromised a carefully + constructed architecture. + +* **他们激励耦合。**你团队的新程序员也许不熟悉你游戏的美妙管理松散耦合的架构,但还是他刚刚获得了他的第一个人物:在岩石撞击地面时播放一段声音。你我都知道这不需要将物理待会和*音频*代码耦合,但是他只想着把他的人物完成。对我们不幸的是,我们的`AudioPlayer`是全局可见的。所以之后一个小小的`#include`,我们的新队员就连累 了整个精心设计的架构。 + + Without a global instance of the audio player, even if he *did* `#include` + the header, he still wouldn't be able to do anything with it. That + difficulty sends a clear message to him that those two modules should not + know about each other and that he needs to find another way to solve his + problem. *By controlling access to instances, you control coupling.* + + 不是用音频播放器的全局实例,哪怕他*确实*`#include`头文件,他还是不能做任何事情。这种难度给他发送了一个明确的信号,这两个模块不应该知道对方,他需要找另外一个方式来解决这个问题。*通过对实例访问权限的控制,你控制了耦合。* + +* **They aren't concurrency-friendly.** The days of games running on a simple + single-core CPU are pretty much over. Code today must at the very least + *work* in a multi-threaded way even if it doesn't take full advantage of + concurrency. When we make something global, we've created a chunk of memory + that every thread can see and poke at, whether or not they know what other + threads are doing to it. That path leads to deadlocks, race conditions, and + other hell-to-fix thread-synchronization bugs. + +* **他们不是并行友好的。**那些游戏在一个简单的单核CPU上运行的日子已经远去。现代的代码至少应考虑使用多线程的方式*工作*,哪怕他完全不需要并行的优势。当我们将某些东西转为全局时,我们创建了一块内存每一个线程都能看到并接触到,不管他们知道不知道其他线程正在使用它。这种方式带来了死锁,竞争状态,以及很难解决的线程同步问题。 + +Issues like these are enough to scare us away from declaring a global variable, +and thus the Singleton pattern too, but that still doesn't tell us how we +*should* design the game. How do you architect a game without global state? + +像这样的问题足够吓阻我们声明全局变量了,同理单例模式也是一样,但是那还没有告诉我们*应该*如何设计游戏。你怎样不使用全局变量来构建游戏? + +There are some extensive answers to that question (most of this book in many +ways *is* an answer to just that), but they aren't apparent or easy to come by. +In the meantime, we have to get games out the door. The Singleton pattern looks +like a panacea. It's in a book on object-oriented design patterns, so it *must* +be architecturally sound, right? And it lets us design software the way we have +been doing for years. + +有很广泛的对这个问题的回答(这本书的大部分都*是*关于这一点的),但是他们还没有回答不容易也不明显获取。与此同时,我们得发布游戏。单例模式看起来是万能药。这是在一本关于面向对象设计模式的书中,因此他*必须*建构声音,对吧?这让我们以我们做了多年的方式设计软件。 + +Unfortunately, it's more placebo than cure. If you scan the list of problems +that globals cause, you'll notice that the Singleton pattern doesn't solve any +of them. That's because a singleton *is* global state -- it's just encapsulated in a +class. + +不幸的是,这不是药剂,这是安慰剂。如果你浏览全局变量造成的问题列表,你会注意到单例模式解决不了任何一个。这是因为单例*是*全局状态——它只是被封装在一个类中。 + +### It solves two problems even when you just have one + +### 他能在你只有一个问题的时候解决两个 + +The word "and" in the Gang of Four's description of Singleton is a bit strange. +Is this pattern a solution to one problem or two? What if we have only one of +those? Ensuring a single instance is useful, but who says we want to let +*everyone* poke at it? Likewise, global access is convenient, but that's true +even for a class that allows multiple instances. + +在GoF的对单例的描述中,“并”这个词有一点奇怪。这个模式是一个问题还是两个问题的解决方案?如果我们只有其中一个问题呢?保证单一的实例是有用的,但是谁告诉我们让*每个人*都能接触到它?同样,全局接触是很方便的,但是对于一个类不允许多个实例是对的么? + +The latter of those two problems, convenient access, is almost always why we +turn to the Singleton pattern. Consider a logging class. Most modules in the +game can benefit from being able to log diagnostic information. However, passing +an instance of our `Log` class to every single function clutters the method +signature and distracts from the intent of the code. + +两问题中的后者,方便地获取,是几乎我们为什么要使用单例模式的原因。考虑一个日志类。大部分模块都能从记录诊断性日志中获益。但是,将一个`Log`类的实例传给每一个需要这个方法的函数混杂了产生的数据,模糊了代码的意图。 + +The obvious fix is to make our `Log` class a singleton. Every function can then +go straight to the class itself to get an instance. But when we do that, we +inadvertently acquire a strange little restriction. All of a sudden, we can no +longer create more than one logger. + +明显的解决方案是让我们的`Log`类成为单例。每一个函数可以直接接触类然后获得一个实例。但是当我们做那个的时候,我们无意地制造了一个小小的奇怪约束。突然之间,我们就不能创建超过一个日志记录者了。 + +At first, this isn't a problem. We're writing only a single log file, so we only +need one instance anyway. Then, deep in the development cycle, we run into +trouble. Everyone on the team has been using the logger for their own +diagnostics, and the log file has become a massive dumping ground. Programmers +have to wade through pages of text just to find the one entry they care about. + +最开始的时候,这不是一个问题。我们写一个单独的日志文件,所以我们只需要一个实例。然后,在开发循环的深处,我们遇到了麻烦。每一个团队的成员都使用日志记录他的自己的诊断信息,日志文件也大量倾泻在地上。程序需要翻过很多页文字只是为了找到他们关心的一个入口。 + +We'd like to fix this by partitioning the logging into multiple files. To do +this, we'll have separate loggers for different game domains: online, UI, audio, gameplay. But we can't. Not only +does our `Log` class no longer allow us to create multiple instances, that +design limitation is entrenched in every single call site that uses it: + +我们想将日志分散道多个文件中解决这一点。为了做到这个,我们为不同的游戏领域创造分散的日志记录者:在线部分,UI,声音,游戏玩法。但是我们不能,不仅仅因为`Log`累不再允许我们创建多个实例,在每次站点使用的单一调用中有设计限制: + + Log::instance().write("Some event."); + +In order to make our `Log` class support multiple instantiation (like it +originally did), we'll have to fix both the class itself and every line of code +that mentions it. Our convenient access isn't so convenient anymore. + +为了让我们的`Log`类支持多个实例(就像他原来的那样),我们需要修改类本身和提及他的每一行代码。我们的方便获取就不再那么方便了。 + + + +### Lazy initialization takes control away from you + +### 惰性初始化从你那里剥夺队列控制权 + +In the desktop PC world of virtual memory and soft performance requirements, +lazy initialization is a smart trick. Games are a different animal. Initializing +a system can take time: allocating memory, loading resources, etc. If +initializing the audio system takes a few hundred milliseconds, we need to +control when that's going to happen. If we let it lazy-initialize itself the +first time a sound plays, that could be in the middle of an action-packed part +of the game, causing visibly dropped frames and stuttering gameplay. + +拥有虚拟内存和软性的性能需求的桌面电脑世界中,惰性初始化是一个小技巧。游戏是另一个生物。初始化系统需要消耗时间:分配内存,加载资源,等等。如果初始化音频系统消耗了几百个毫秒,我们需要控制发生的东西。如果我们在第一次声音播放时惰性初始化它自己,这可能是在动作部分的中间,但是可见的掉帧和结巴的游戏体验。 + +Likewise, games generally need to closely control how memory is laid out in the +heap to avoid fragmentation. If our audio +system allocates a chunk of heap when it initializes, we want to know *when* +that initialization is going to happen, so that we can control *where* in the +heap that memory will live. + +同样,游戏通常需要严格控制在堆上分配的内存来避免碎片。如果我买单音频系统在初始化时分配到了堆上的一,我们需要知道“何时”初始化发生了,所以我们可以控制内存会待在堆的“哪里”。 + + + +Because of these two problems, most games I've seen don't rely on lazy +initialization. Instead, they implement the Singleton pattern like this: + +因为这两个原因,我见到的大多数游戏都不依赖惰性初始化。相反,他们像这样实现单例模式: + +^code 5 + +That solves the lazy initialization problem, but at the expense of discarding +several singleton features that *do* make it better than a raw global variable. +With a static instance, we can no longer use polymorphism, and the class must be +constructible at static initialization time. Nor can we free the memory that the +instance is using when not needed. + +者解决了惰性初始化问题,但是损失了几个单例比原生的全局变量优良的特性。通过一个静态实例,我们不能使用多态了,类也必须是在静态初始化时间就可以构建的。我们也不能在不需要的时候释放实例使用的的内存。 + +Instead of creating a singleton, what we really have here is a simple static +class. That isn't necessarily a bad thing, but if a static class is all you +need, why not get rid of the `instance()` method +entirely and use static functions instead? Calling `Foo::bar()` is simpler than +`Foo::instance().bar()`, and also makes it clear that you really are dealing +with static memory. + +不是创建一个单例,我们这里真正有的是一个简单的静态类。这不是一个坏事情,但是乳沟一个静态类是你需啊哟的,为什么不完全摆脱`instance()`方法直接使用静态函数呢?调用`Foo::bar()`比`Foo::instance().bar()`更简单,也更明确表明你在处理静态内存。 + + + +## What We Can Do Instead + +## 我们可以做什么 + +If I've accomplished my goal so far, you'll think twice before you pull +Singleton out of your toolbox the next time you have a problem. But you still +have a problem that needs solving. What tool *should* you pull out? Depending on +what you're trying to do, I have a few options for you to consider, but first... + +如果我现在完成队列我的目标,你就会在下一个遇到问题使用单例模式之前三思而后行。但是你还是有问题需要解决。什么工具你*应该*使用呢?取决于你在试图做什么,我有你可以考虑的一些选项,但是首先…… + +### See if you need the class at all + +### 看看你是不是真正的需要类 + +Many of the singleton classes I see in games are "managers" -- those nebulous +classes that exist just to babysit other objects. I've seen codebases where it +seems like *every* class has a manager: Monster, MonsterManager, Particle, +ParticleManager, Sound, SoundManager, ManagerManager. Sometimes, for variety, +they'll throw a "System" or "Engine" in there, but it's still the same idea. + +我在游戏中看到的很多单例类都是“管理器”——那些模糊的类存在的意义就是照顾其他对象。我看到代码库中好像*所有*类都有一个管理器:怪物,怪物管理器,粒子,粒子管理器,声音,声音管理器,管理管理器的管理器。优势,更多情况下,他们会在那里放一个“系统”或“引擎”但是还是一样的思路。 + +While caretaker classes are sometimes useful, often they just reflect +unfamiliarity with OOP. Consider these two contrived classes: + +当小心处理类有时是有用的,通常他们只是反映对OOP的不熟悉。思考这两个人为的类: + +^code 8 + +Maybe this example is a bit dumb, but I've seen plenty of code that reveals a +design just like this after you scrape away the crusty details. If you look at +this code, it's natural to think that `BulletManager` should be a singleton. After +all, anything that has a `Bullet` will need the manager too, and how many +instances of `BulletManager` do you need? + +也许这个例子有些蠢,但是我看到了很多代码在扫去那些细节后都是一样的设计。乳沟你看看这个代码,`BulletManager`很自然应该是一个单例。无论如何,任何有`Bullet`的都需要管理,而你需要多少`BulletManager`实例呢? + +The answer here is *zero*, actually. Here's how we solve the "singleton" problem +for our manager class: + +这里的回答是*零*,事实上。这是我们如何为我们的管理类解决你的“单例”问题: + +^code 9 + +There we go. No manager, no problem. Poorly designed singletons are often +"helpers" that add functionality to another class. If you can, just move all of +that behavior into the class it helps. After all, OOP is about letting objects +take care of themselves. + +好了。没有管理员,没问题。糟糕设计的单例通常“帮助”另一个类增加代码。如果你可以的话,把所有的行为都移到类中。无论如何,OOP是关于然对象照顾好他自己。 + +Outside of managers, though, there are other problems where we'd reach to +Singleton for a solution. For each of those problems, there are some alternative +solutions to consider. + +但是在管理器之外,还有其他问题我们需要寻求单例解决。对于每一种问题,都有一些后续方案来考虑。 + +### To limit a class to a single instance + +### 将一个类限制到一个单一的实例 + +This is one half of what the Singleton pattern gives you. As in our file system +example, it can be critical to ensure there's only a single instance of a class. +However, that doesn't necessarily mean we also want to provide *public*, +*global* access to that instance. We may want to restrict access to certain +areas of the code or even make it private to a +single class. In those cases, providing a public global point of access weakens +the architecture. + +这是单例模式给你的一半东西。就像在我们文件系统的例子中那样,保证一个类只有一个实例是很重要的。但是,这不意味着我们需要*公开*提供,对那个实例的*全局*访问。我们想要减少对代码某部分的接触设置让他对一个类是私有的。在这些情况下,提供一个全局接触点消弱了架构。 + + + +We want a way to ensure single instantiation *without* providing global access. +There are a couple of ways to accomplish this. Here's one: + +我们想要一种方式保证一个实例而*无需*提供全局接触。这里有好几种方法完成。这是其中之一: + + + +^code 6 + +This class allows anyone to construct it, but it will assert and fail if you try to +construct more than one instance. As long as the right code creates the instance +first, then we've ensured no other code can either get at that instance or +create their own. The class ensures the single instantiation requirement it +cares about, but it doesn't dictate how the class should be used. + +这个类运行任何人构建它,乳沟你试图构建多余一个实例,它会断言并失败。只要争取的代码首先创建了实例,那么我们就得保证没有其他代码可以接触实例或者创建他们之间的。这个类保证了他关注的单一实例需求,但是没有指定类该如何被使用。 + + + +The downside with this implementation is that the check to prevent multiple +instantiation is only done at *runtime*. The Singleton pattern, in contrast, +guarantees a single instance at compile time by the very nature of the class's +structure. + +这个事项的缺点是检查并防止多个实例化只在*运行时*进行。单例模式,相反,抱着在编译时就确定单一实例是类的自然结构。 + +### To provide convenient access to an instance + +### 为了给一个实例提供方便的获取方法 + +Convenient access is the main reason we reach for singletons. They make it easy +to get our hands on an object we need to use in a lot of different places. That +ease comes at a cost, though -- it becomes equally easy to get our hands on the +object in places where we *don't* want it being used. + +方便的获取是我们使用单例的一个主要原因。这让我们在很多不同的地方获取一个我们需要的对象更加容易。这种轻松是需要代价的——它很容易个让我们在我们*不想*要对象使用的地方被使用。 + +The general rule is that we want variables to be as narrowly scoped as possible +while still getting the job done. The smaller the scope an object has, the fewer +places we need to keep in our head while we're working with it. Before we take +the shotgun approach of a singleton object with *global* scope, let's consider +other ways our codebase can get access to an object: + +通用原则是我们想让变量子啊能完成工作的情况写尽可能局部。对象影响的范围越小,我们在处理它的时候就越少需要把东西放在脑子里。在我们拿起有*全局*范围影响的单例对象猎枪时,让我们考虑其他我们代码库能够获取对象的方式: + + * **Pass it in.** The simplest solution, and often the + best, is to simply pass the object you need as an argument to the functions + that need it. It's worth considering before we discard it as too cumbersome. + + * **传进来。**最简单的解决办法,通常也是最好的,简单的把你需要的对象作为参数传给需要它的函数。在我们用其他更加繁杂的方法前考虑这个解决方案。 + + + + Consider a function for rendering objects. In order to render, it needs + access to an object that represents the graphics device and maintains the + render state. It's very common to simply pass that in to all of the rendering + functions, usually as a parameter named something like `context`. + + 考虑一个渲染对象的函数。为了渲染,他需要接触一个代表图形设备的对象,管理渲染状态。将其传给所有渲染函数通常是很自然的,通常是一个参数名字像是`context`之类的。 + + On the other hand, some objects don't belong in the signature of a method. + For example, a function that handles AI may need to also write to a log file, but logging isn't its core concern. It would be + strange to see `Log` show up in its argument list, so for cases like that + we'll want to consider other options. + + 另一方面,游戏对象不属于方法的署名。举个例子,一个出来AI的函数可能也需要写日志文件,但是日志不是他的核心关注点。看到`Log`出现在它的参数列表中是很奇怪的事情,由于像这样的情况我们需要考虑其他的选项。 + + + + * **Get it from the base class.** Many game architectures have shallow but + wide inheritance hierarchies, often only one level deep. For example, you + may have a base `GameObject` class with derived classes for each enemy or + object in the game. With architectures like this, a large portion of the + game code will live in these "leaf" derived classes. This means that all + these classes already have access to the same thing: their `GameObject` base + class. We can use that to our advantage: + + **从基类中取出。**很逗游戏架构有浅层但是广泛的继承层次,通常只有一层深。举个例子,你也许有一个基础的`GameObject`类拥有每一个游戏中的敌人或者对象的推到类。通过使用这样的架构,很大一部分游戏代码会存在这些“子”推导类中。这就意味着其他这些类已经有啦对同样事物的相同获取方法:他们的`GameObject`基类。我们可以将其作为优势: + + + + ^code 10 + + This ensures nothing outside of `GameObject` has access to its `Log` object, + but every derived entity does using `getLog()`. This pattern of letting + derived objects implement themselves in terms of protected methods provided + to them is covered in the Subclass Sandbox chapter. + + 这没有保证任何`GameObject`外部的东西可以获得他的`Log`对象,但是每一个推导的实体确实使用`getLog()`。这种通过提供给他们的保护方法让推导对象实现他们的模式在子类沙箱这章中被覆盖到了。 + + + + * **Get it from something already global.** The goal of removing *all* global + state is admirable, but rarely practical. Most codebases will still have a + couple of globally available objects, such as a single `Game` or `World` + object representing the entire game state. + + * **从已经是全局的东西中获取。**移除*所有*全局状态的目标令人钦佩,但是并不实际。大多数代码库还是会有一些可用全局对象,比如一个`Game`或`World`对象代表了整个游戏的状态。 + + We can reduce the number of global classes by piggybacking on existing ones + like that. Instead of making singletons out of `Log`, `FileSystem`, and + `AudioPlayer`, do this: + + 我们可以通过让现有的对象捎带来减少全局变量类的数目。不是让`Log`,`FileSystem`和`AudioPlayer`变成单例,像这样做: + + ^code 11 + + With this, only `Game` is globally available. Functions can get to the + other systems through it: + + 通过这样,只有`Game`是全局可用的。需要到其他系统的函数得穿过它: + + ^code 12 + + + + If, later, the architecture is changed to support multiple `Game` instances + (perhaps for streaming or testing purposes), `Log`, `FileSystem`, and + `AudioPlayer` are all unaffected -- they won't even know the difference. The + downside with this, of course, is that more code ends up coupled to `Game` + itself. If a class just needs to play sound, our example still requires it + to know about the world in order to get to the audio player. + + 如果,稍后,架构被改为支持多个`Game`实例(可能是为了流或者测试目的),`Log`,`FileSystem`,和`AudioPlayer`不会被影响到——他们甚至不知道有什么区别。这一点的缺陷是,单人,更多的代码耦合到了`Game`中。乳沟一个类只是需要播放声音,我们的例子仍然需要它知道游戏世界,只为了获得音频播放器。 + + We solve this with a hybrid solution. Code that already knows about `Game` + can simply access `AudioPlayer` directly from it. For code that doesn't, we + provide access to `AudioPlayer` using one of the other options described + here. + + 我们通过混合方案解决这一点。知道`Game`的代码可以直接从那里获得`AudioPlayer`,对于不知道的代码,我们用上面描述的其他选项来提供`AudioPlayer`。 + + * **Get it from a Service Locator.** So far, we're assuming the global class + is some regular concrete class like `Game`. Another option is to define a + class whose sole reason for being is to give global access to objects. This + common pattern is called a Service Locator and gets its own chapter. + + * **从服务定位器中获得。**目前为止,我们假设全局类是通常具体的类比如`Game`,另外一种选项是定义一个类,唯一的目标就是为对象提供全局访问。这种常见的模式被称为服务定位器模式,有它单独的章节。 + +## What's Left for Singleton + +## 单例中还剩下什么 + +The question remains, where *should* we use the real Singleton pattern? +Honestly, I've never used the full Gang of Four implementation in a game. To +ensure single instantiation, I usually simply use a static class. If that +doesn't work, I'll use a static flag to check at runtime that only one instance +of the class is constructed. + +剩下的问题,何处我们*应该*使用真实的单例模式?说实话,我从来没有在游戏中完全使用全部的GoF。wield保证单一实例化,我通常简单的使用一个静态类。如果这没有效果,我使用一个静态标识在运行时检测是不是只有一个实例被创建了。 + +There are a couple of other chapters in this book that can also help here. The +Subclass Sandbox pattern +gives instances of a class access to some shared state without making it +globally available. The Service +Locator pattern *does* make an object globally available, but it gives you more +flexibility with how that object is configured. + +这本书还有几个章节也可以帮忙。子类沙箱模式通过一些分享状态来给实例一个类的访问权限而无需让其全局可用。服务定位器模式*确实*让一个对象全局可用队列,但它给了你更大的对象如何设置的自由度。 diff --git a/book/spatial-partition.markdown b/book/spatial-partition.markdown new file mode 100644 index 0000000..741549f --- /dev/null +++ b/book/spatial-partition.markdown @@ -0,0 +1,796 @@ +^title Spatial Partition +^section Optimization Patterns + +## Intent + +## 意图 + +*Efficiently locate objects by storing them in a data structure organized +by their positions.* + +*将对象根据他们的位置存储在数据结构中,来有效的定位对象。* + +## Motivation + +## 动机 + +Games let us visit other worlds, but those worlds typically aren't so different +from our own. They often share the same basic physics and tangibility of our +universe. This is why they can feel real despite being crafted of mere bits and +pixels. + +游戏让我们的拜访其他世界,但这些世界通常和我买单没有什么不同。他们通常有和我们宇宙同样的基础物理和感知系统。这就是我们为什么会认为这些由比特和像素构建的东西是真实的。 + +One bit of fake reality that we'll focus on here is *location*. Game worlds have +a sense of *space*, and objects are somewhere in that space. This manifests +itself in a bunch of ways. The obvious one is physics -- objects move, collide, +and interact -- but there are other examples. The audio engine may take into +account where sound sources are relative to the player so that distant sounds +are quieter. Online chat may be restricted to nearby players. + +我们这里注意的的一个假事实是*位置*。游戏世界有*空间*的感觉,对象都在空间的某处。它用很多种方式证明这一点。最明显的是物理——对象移动,碰撞,交换——但是这里还有其他例子。音频引擎也许会根据声源和玩家的距离来确定远处的声音更小。在线交流也许局限在较近的玩家之间。 + +This means your game engine often needs to answer to the question, "What objects +are near this location?" If it has to answer this enough times each frame, it +can start to be a performance bottleneck. + +这意味着你的游戏引擎通常需要回答这个问题,“那些对象在这个位置周围?”如果每一帧都需要回答这个问题,这就会变成一个性能瓶颈。 + +### Units on the field of battle + +### 在战场上的单位 + +Say we're making a real-time strategy game. Opposing armies with hundreds of +units will clash together on the field of battle. Warriors need to know which +nearby enemy to swing their blades at. The naïve way to handle this is by looking +at every pair of units and seeing how close they are to each other: + +假设我们在做一个实施战略游戏。双方成百上千的单位在战场上撞在一起。战士需要知道最近的是哪一个敌人去挥舞刀锋所向。最简单的处理方法是检查每一对单位,然后看看他们互相之间有多么近: + +^code pairwise + +Here we have a doubly nested loop where each loop is walking all of the units on the battlefield. That means the number of +pairwise tests we have to perform each frame increases with the *square* of the +number of units. Each additional unit we add has to be compared to *all* of the +previous ones. With a large number of units, that can spiral out of control. + +现在我们有了双重内嵌循环,每一个循环都会遍历战场上的所有敌人。这就是围着在每一帧进行的测试对数会随着单位数量的*平方*增长。每一个附加单位都需要和*所有*之前的相比较。如果有大量单位,这就完全失控了。 + + + +### Drawing battle lines + +### 描绘战线 + +The problem we're running into is that there's no underlying order to the array +of units. To find a unit near some location, we have to walk the entire array. +Now, imagine we simplify our game a bit. Instead of a 2D battle*field*, imagine +it's a 1D battle*line*. + +我们这里碰到的问题是这里没有指明数组中潜藏的对象顺序。为了在某个位置附近找到一个单位,我们需要遍历整个数组。现在,我们简化一下游戏。不使用2D的战*场*,想象这是一个1D的战*线*。 + +A number line with Units positioned at different coordinates on it. + +In that case, we could make things easier on ourselves by *sorting* the array of +units by their positions on the battleline. Once we do that, we can use something +like a [binary +search](http://en.wikipedia.org/wiki/Binary_search) to find nearby units without +having to scan the entire array. + +在这种情况下,我们可以通过根据他们在战线上的位置*排序*数组元素来让问题简化。一旦我们做了这个,我们可以使用使用像二分查找之类的东西找到最近的对象而不必扫描整个数组。 + + + +The lesson is pretty obvious: if we store our objects in a data structure +organized by their locations, we can find them much more quickly. This pattern +is about applying that idea to spaces that have more than one dimension. + +这里的教学很简单:如果我们根据他们的位置存储组织数据结构中的对象,我们可以更快的找到他们。这个事实是关于将这个思路应用到超过一个维度上。 + +## The Pattern + +## 模式 + +For a set of **objects**, each has a **position in space**. Store them in a +**spatial data structure** that organizes the objects by their positions. This +data structure lets you **efficiently query for objects at or near a location**. +When an object's position changes, **update the spatial data structure** so that +it can continue to find the object. + +对于一系列**对象**,每一个都有**空间上的位置**。将他们存储在一个根据位置组织对象的**空间数据结构**中,让你**有效的查询在某处或者附近的对象**。当对象的位置改变时,**更新空间数据结构**,这样它可以继续找到对象。 + +## When to Use It + +## 何时使用 + +This is a common pattern for storing both live, moving game objects and also the +static art and geometry of the game world. Sophisticated games often have +multiple spatial partitions for different kinds of content. + +这是一个常用的模式来存储活跃的,移动的游戏对象,还有静态艺术和游戏世界的地理。负载的游戏通常为不同的内容有不同的空间划分。 + +The basic requirements for this pattern are that you have a set of objects that +each have some kind of position and that you are doing enough queries to find +objects by location that your performance is suffering. + +这个模式的基本需求是你有一系列对象每一种都有位置,你性能煎熬的时候需要做足够多的查询来通过位置寻找对象。 + +## Keep in Mind + +## 记住 + +Spatial partitions exist to knock an *O(n)* or *O(n²)* operation down to +something more manageable. The *more* objects you have, the more valuable that +becomes. Conversely, if your *n* is small enough, it may not be worth the +bother. + +空间划分的存在是为了将一个*O(n)*或者*O(n²)*的操作降到更加能控制的数量级。你拥有的对象*越多*,这就越重要。相反的,如果你的*n*足够小,也许不需要担心这个。 + +Since this pattern involves organizing objects by their positions, objects that +*change* position are harder to deal with. You'll have to reorganize the data structure to keep track of an +object at a new location, and that adds code complexity *and* spends CPU cycles. +Make sure the trade-off is worth it. + +由于这个模式包含了通过位置组织对象,可以*改变*位置的对象更难处理。你需要重新组织数据结构来保证追踪一个在新位置的对象,这添加了更多的复杂性*和*消耗CPU循环。保证交易是值得的。 + + + +A spatial partition also uses additional memory for its bookkeeping data +structures. Like many optimizations, it trades memory for speed. If you're +shorter on memory than you are on clock cycles, that may be a losing +proposition. + +一个空间划分也会因为记录划分的数据结构而使用附加的内存。就像很多优化一样,它用内存换速度。如果你比时钟周期更加短缺内存,这就是个失败的提议。 + +## Sample Code + +## 示例代码 + +The nature of patterns is that they *vary* -- each implementation will be a bit +different, and spatial partitions are no exception. Unlike other patterns, +though, many of these variations are +well-documented. Academia likes publishing papers that prove performance gains. +Since I only care about the concept behind the pattern, I'm going to show you +the simplest spatial partition: a *fixed grid*. + +模式的自然是他们*变化*——每一种实现都略有不同,空间划分也不例外。不像其他的模式,他们的变化都可以很好的记录下来。像发表学术文章那样证明性能优势。由于我只关注模式背后的观念,我会给你展示一个最简单的划分:一个*固定网格*。 + + + +### A sheet of graph paper + +### 一张网格纸 + +Imagine the entire field of battle. Now, superimpose a grid of fixed-size squares +onto it like a sheet of graph paper. Instead of storing our units in a single +array, we put them in the cells of this grid. Each cell stores the list of units +whose positions are within that cell's boundary. + +想象一下整个战场。现在,叠加一张固定大小的网格方块子啊上面就好像一张网格纸。不是在一个单独的数组中存储我们的对象,我们将他们存到网格的格子中。每个格子存储一列单位,他们的位置在格子的边界内部。 + +A grid with Units occupying different cells. Some cells have multiple Units. + +When we handle combat, we only consider units within the same cell. Instead of +comparing each unit in the game with every other unit, we've *partitioned* the +battlefield into a bunch of smaller mini-battlefields, each with many fewer +units. + +当我们处理战斗时,我们只需要考虑在同一个格子中的单位。不是将每个游戏中的单位与其他所有单位比较,我们将战场*划分*为多个小的战场,每个都有更少的单位。 + +### A grid of linked units + +### 一网格链接单位 + +OK, let's get coding. First, some prep work. Here's our basic `Unit` class: + +好了,让我们编码把。首先,一些准备工作。这是我们的基础`Unit`类。 + +^code unit-simple + +Each unit has a position (in 2D) and a pointer to the `Grid` that it lives on. +We make `Grid` a `friend` class because, as we'll see, when a unit's position +changes, it has to do an intricate dance with the grid to make sure everything +is updated correctly. + +每一个单位都有一个位置(2D表示),以及一个指针指向它存在的`Grid`。我们让`Grid`成为一个`friend`类,因为,就像我们将要看到的,但一个单位的位置改变时,他需要和网格做复杂的交互以确保任何事情都正确的更新了。 + +Here's a sketch of the grid: + +这里是网格表示: + +^code grid-simple + +Note that each cell is just a pointer to a unit. Next, +we'll extend `Unit` with `next` and `prev` pointers: + +注意每一个玩个是一个指向单位的指针。下面我们扩展`Unit`,增加`next`和`prev`指针: + +^code unit-linked + +This lets us organize units into a [doubly linked +list](http://en.wikipedia.org/wiki/Doubly_linked_list) instead of an array. + +这让我们将对象组织为双向链表而不是数组。 + +A Cell pointing to a a doubly linked list of Units. + +Each cell in the grid points to the first unit in the list of units within that +cell, and each unit has pointers to the units before it and after it in the +list. We'll see why soon. + +每个网格中的指针都指向在网格中的元素列表的第一个,每一个对象都有一个指针指向前面,以及一个指向列表中它的后面。我们等会会知道为什么。 + + + +### Entering the field of battle + +### 进入战场 + +The first thing we need to do is make sure new units are actually placed into +the grid when they are created. We'll make `Unit` handle this in its +constructor: + +我们需要做的第一件事是保证新单位在创建时被放置到了网格中。我们让`Unit`在它的构建器中处理这个: + +^code unit-ctor + +This `add()` method is defined like so: + +`add()`方法像这样定义: + + + +^code add + + + +It's a little finicky like linked list code always is, but the basic idea is +pretty simple. We find the cell that the unit is sitting in and then add it to +the front of that list. If there is already a list of units there, we link it in +after the new unit. + +就像链表通常的那样有些繁琐,基本思路是非常简单的。我们找到单位所在的网格然后将它添加到列表前部。如果已经有一个列表单位了,我们把它链接到新的后面。 + +### A clash of swords + +### 刀剑碰撞 + +Once all of the units are nestled in their cells, we can let them start hacking +at each other. With this new grid, the main method for handling combat looks like +this: + +一旦所有的单位都安定在他们的网格中,我们可以让他们开始互相交互。使用这个新网格,处理战斗的主要方法看上去是这样的: + +^code grid-melee + +It walks each cell and then calls `handleCell()` on it. As you can see, we +really have partitioned the battlefield into little isolated skirmishes. Each +cell then handles its combat like so: + +它遍历每一个网格然后在它上面调用`handleCell()`。就像你看到的那样,我们真的需要将战场分割为小的分离的冲突。每个网格之后像这样处理它的战斗: + +^code handle-cell + +Aside from the pointer shenanigans to deal with walking a linked list, note that +this is exactly like our original naïve method for +handling combat. It compares each pair of units to see if they're in the same +position. + +除了遍历链表的指针把戏,注意它和我们原先处理战斗的原始方法完全一样。他对比每一对单位看看他们是否在同一个位置。 + +The only difference is that we no longer have to compare *all* of the units in +the battle to each other -- just the ones close enough to be in the same cell. +That's the heart of the optimization. + +不同之处是,我们不必再互相比较战场上*所有的*单位——只与那些近在一个格子中的相比较。这就是优化的核心。 + + + +### Charging forward + +### 加油向前 + +We've solved our performance problem, but we've created a new problem in its +stead. Units are now stuck in their cells. If we move a unit past the boundary +of the cell that contains it, units in the cell won't see it anymore, but +neither will anyone else. Our battlefield is a little *too* partitioned. + +我们解决了我们的性能问题,但是我们在它的好处中创建了新的问题。单位现在陷在它的格子中。如果我们将单位移过了包含它的格子,在格子中的单位就再也看不到它了,但是其他人也看不到了。我们的战场有点*过度*划分了。 + +To fix that, we'll need to do a little work each time a unit moves. If it +crosses a cell's boundary lines, we need to remove it from that cell and add it +to the new one. First, we'll give `Unit` a method for changing its position: + +为了解决那个,我们需要每一次单位移动的时候都做一些工作。如果它跨越了格子的边界,我们需要将它从原来的格子删除,添加到新的格子中。首先,我们给`Unit`一个方法来改变它的位置: + +^code unit-move + +Presumably, this gets called by the AI code for computer-controlled units and by +the user input code for the player's units. All it does is hand off control to +the grid, which then does: + +可推测的是,它会被AI代码调用控制电脑的单位,也会被玩家输入代码调用来控制玩家的单位。它做的只是交换格子的控制权,之后: + +^code grid-move + +That's a mouthful of code, but it's pretty straightforward. The first bit checks +to see if we've crossed a cell boundary at all. If not, all we need to do is +update the unit's position and we're done. + +这是一大块代码,但它很直观。第一位检查我们是否穿越了格子的边界。如果没有,我们需要做的所有事情就是更新单位的位置然后搞定了。 + +If the unit *has* left its current cell, we remove it from that cell's linked +list and then add it back to the grid. Like with adding a new unit, that will +insert the unit in the linked list for its new cell. + +如果单位*已经*离开了它现在的格子,我们从格子的链表中移除它,然后再添加到网格中。就像天机一个新单位,它会插入新格子的链表。 + +This is why we're using a doubly linked list -- we can very quickly add and remove +units from lists by setting a few pointers. With lots of units moving around +each frame, that can be important. + +这就是为什么我们使用双向链表——我们可以通过设置一些指针飞快的添加和删除单位。每帧都有很多单位移动,这就很重要了。 + +### At arm's length + +### 一臂之长 + +This seems pretty simple, but I have cheated in one way. In the example I've +been showing, units only interact when they have the *exact same* position. +That's true for checkers and chess, but less true for more realistic games. +Those usually have attack *distances* to take into account. + +这看起来很简单,但我们某种程度上作弊了。在我展示的例子中,单位在他们有*完全相投的*位置时才交互。这对于西洋棋和国际象棋是真的,但是对于更加实际的游戏就不那么准确了。这通常需要将攻击*距离*引入考虑。 + +This pattern still works fine. Instead of just checking for an exact location +match, we'll do something more like: + +这个模式仍然可以好好工作,与检查同一位置匹配相反,我们做的事情更接近于: + +^code handle-distance + +When range gets involved, there's a corner case we need to consider: units in +different cells may still be close enough to interact. + +当范围被牵扯进来时,有一个边界情况需要我们考虑:在不网格的单位也许仍然足够接近可以相互交流。 + +Two Units in adjacent Cells are close enough to interact. + +Here, B is within A's attack radius even through their centerpoints are in +different cells. To handle this, we will need to compare units not only in the +same cell, but in neighboring cells too. To do this, first we'll split the inner +loop out of `handleCell()`: + +这里,B在A的攻击半径内,哪怕他们的中心点在不同的网格。为了处理这个,我们不仅需要比较同一网格的单位,同时需要比较临近的网格。为了达到这一点,首先我们将内层循环摆脱`handleCell()`: + +^code handle-unit + +Now we have a function that will take a single unit and a list of other units +and see if there are any hits. Then we'll make `handleCell()` use that: + +现在我们有一个函数去一个单位和一列表的其他单位看看有没有碰撞。然后我们让`handleCell()`使用这个: + +^code handle-cell-unit + +Note that we now also pass in the coordinates of the cell, not just its unit +list. Right now, this doesn't do anything differently from the previous example, +but we'll expand it slightly: + +注意我们同样传入了网格的坐标,而不仅仅是对象列表。现在,这也许和前面的例子没有什么区别,但是我们会稍微扩展它: + +^code handle-neighbor + +Those additional `handleUnit()` calls look for hits between the current unit and +units in four of the eight neighboring cells. If +any unit in those neighboring cells is close enough to the edge to be within the +unit's attack radius, it will find the hit. + +这些新加的`handleCell()`调用AI现在的单位和周围八个临近格子中的四个格子寻找是否有任何碰撞。如果任何邻近格子的单位离边缘足够近到单位的攻击半径内,它会找到碰撞。 + + + +We only look at *half* of the neighbors for the same reason that the inner loop +starts *after* the current unit -- to avoid comparing each pair of units twice. +Consider what would happen if we did check all eight neighboring cells. + +我们只查询*一半*的近邻,基于内层循环从当前单位*之后*的单位开始这一原因——避免将每对单位比较两次。考虑如果我们检查全部八个近邻格子会发生什么。 + +Let's say we have two units in adjacent cells close enough to hit each other, +like the previous example. Here's what would happen if we looked at all eight cells surrounding each unit: + +假设我们有两个在邻近格子的单位靠的足够近可以互相攻击,就向前一个例子。这是我们检查全部8个格子会发生的事情: + + 1. When finding hits for A, we would look at its neighbor on the right + and find B. So we'd register an attack between A and B. + + 2. Then, when finding hits for B, we would look at its neighbor on the + *left* and find A. So we'd register a *second* attack between A and B. + + 1. 当找谁打了A的时候,我们检查他的右边找到了B。所以我们注册一次A和B之间的攻击。 + + 2. 然后,当找谁打了B时,我们检查他的*左边*找到了A。所以我吗注册了*第二次*A和B之间的攻击。 + +Only looking at half of the neighboring cells fixes that. *Which* half we look +at doesn't matter at all. + +只检查一般的近邻格子修复了这一点。检查*哪一*半无关紧要。 + +There's another corner case we may need to consider too. Here, we're assuming +the maximum attack distance is smaller than a cell. If we have small cells and +large attack distances, we may need to scan a bunch of neighboring cells several +rows out. + +还有另外一个边界情况我们需要考虑。这里,我们假设最大攻击距离小于一个格子。如果我们有小格子和长攻击距离,我们预习需要扫描几行外的近邻格子。 + +## Design Decisions + +## 设计决策 + +There's a relatively short list of well-defined spatial partitioning data +structures, and one option would be to go through them one at a time here. +Instead, I tried to organize this by their essential characteristics. My hope is +that once you do learn about quadtrees and binary space partitions (BSPs) and +the like, this will help you understand *how* and *why* they work and why you +might choose one over the other. + +设计好的位置划分数据结构的原则互相相关,说明方法之一是一个一个说。但是,我试图从他们的本质特性来组织这些。我的期望是一旦你学会了四叉树和二分空间查找(BSPs)和其他类似东西,这可以帮助你理解他们是*如何*工作,*为什么*工作,以帮你选择。 + +### Is the partition hierarchical or flat? + +### 划分是层次的还是平面的? + +Our grid example partitioned space into a single flat set of cells. In contrast, +hierarchical spatial partitions divide the space into just a couple of regions. Then, if one of these regions still +contains many objects, it's subdivided. This process continues recursively until +every region has fewer than some maximum number of objects in it. + +我们的网格例子将空间划分成一层格子的集合。相反,层次的空间划分将空间分成几个区域。然后,如果其中一个区域还包含多个对象,再划分它。这个过程递归进行知道每一个区域都有少于最大数量的对象再其中。 + + + + * **If it's a flat partition:** + + * **如果是平面划分:** + + * *It's simpler.* Flat data structures are + easier to reason about and simpler to implement. + + * *简单。*平面数据结构是最容易想到的也更简单实现。 + + + + * *Memory usage is constant.* Since adding new objects doesn't require + creating new partitions, the memory used by the spatial partition can + often be fixed ahead of time. + + * *内存使用时常量。*由于添加新的对象不需要添加新的划分,空间划分的内存使用通常在之前就可以确定。 + + * *It can be faster to update when objects change their positions.* When + an object moves, the data structure needs to be updated to find the + object in its new location. With a hierarchical spatial partition, this + can mean adjusting several layers of the hierarchy. + + * *在对象改变他们的位置时更快更新。*当一个对象移动,数据结构需要更新来找到他在哪个位置。通过层次性空间划分,这一位置在很多层间调整位置。 + + * **If it's hierarchical:** + + * **如果是层次性的:** + + * *It handles empty space more efficiently.* Imagine in our earlier + example if one whole side of the battlefield was empty. We'd have a + large number of empty cells that we'd still have to allocate memory for + and walk each frame. + + * *它更有效率的处理空的空间。*考虑我们之前的例子,如果战场的一层是空洞。我们需要分配一堆空的格子,我们要在他们上面浪费内存,每帧还要遍历他们。 + + Since hierarchical space partitions don't subdivide sparse regions, a + large empty space will remain a single partition. Instead of lots of + little partitions to walk, there is a single big one. + + 由于层次空间划分不再分割空的区域,汉代的空空间保持在一个划分上。不需要很多遍历小空间,那里只有一个大的。 + + * *It handles densely populated areas more efficiently.* This is the other + side of the coin: if you have a bunch of objects all clumped together, a + non-hierarchical partition can be ineffective. You'll end up with one + partition that has so many objects in it that you may as well not be + partitioning at all. A hierarchical partition will adaptively subdivide + that into smaller partitions and get you back to having only a few + objects to consider at a time. + + * *它处理浓密空间更加有效率。*这就是硬币的另一面了:如果你有一堆对象堆在一起,一个无层次的划分没有效率。你最总将所有对象都划分到了一起就跟没有划分一样。一个层次性的划分会适应地划成小块然后让你同时只考虑一点对象。 + +### Does the partitioning depend on the set of objects? + +### 划分依赖于对象集合吗? + +In our sample code, the grid spacing was fixed beforehand, and we slotted units +into cells. Other partitioning schemes are adaptable -- they pick partition +boundaries based on the actual set of objects and where they are in the world. + +在我们的实例代码中,网格空间事先被固定了,我们在格子里追踪单位。另外的划分策略是适应性的——他们根据现有的对象集合在世界中的位置划分边界。 + +The goal is have a *balanced* partitioning where each region has roughly the +same number of objects in order to get the best performance. Consider in our +grid example if all of the units were clustered in one corner of the +battlefield. They'd all be in the same cell, and our code for finding attacks +would regress right back to the original *O(n²)* problem that we're trying +to solve. + +目标是有一个*平衡的*划分每个区域有相同的单位数量以获得最好性能。考虑网格的例子,如果所有的安慰都记载战场的角落里。他们会都在同一个格子中,我们找寻攻击的代码退回原来的*O(n²)*问题。 + + * **If the partitioning is object-independent:** + + * **如果划分与对象无关:** + + * *Objects can be added incrementally.* Adding an object means finding the + right partition and dropping it in, so you can do this one at a time + without any performance issues. + + * *对象可以增量添加。*添加一个对象意味着找到正确的划分然后放入其中,这点可以一次完成,没有任何性能问题。 + + * *Objects can be moved quickly.* With fixed partitions, moving a unit + means removing it from one and adding it to another. If the partition + boundaries themselves change based on the set of objects, then moving + one can cause a boundary to move, which can in + turn cause lots of other objects to need to be moved to different + partitions. + + * * 对象移动的更快。*通过固定的划分,移动一个单位意味着从一个格子移除然后添加到另外一个。如果划分他们的边界基于对对象集合而改变,那么移动一个对象会引起边界移动,导致很多其他对象也许哟移到其他划分。 + + + + * *The partitions can be imbalanced.* Of course, the downside of this + rigidity is that you have less control over your partitions being evenly + distributed. If objects clump together, you get worse performance there + while wasting memory in the empty areas. + + * *划分也许不平衡。*当然,固定的缺点就是你对你的划分缺少控制。如果对象记载一起,你浪费了内存在空区域上,这会造成更糟的性能。 + + * **If the partitioning adapts to the set of objects:** + + * **如果划分适应对象集合:** + + Spatial partitions like BSPs and k-d trees split the world recursively so + that each half contains about the same number of objects. To do this, you + have to count how many objects are on each side when selecting the planes + you partition along. Bounding volume hierarchies are another type of spatial + partition that optimizes for the specific set of objects in the world. + + 空间划分像BSPs和k-d树这样队规切分世界让每一部分都包含相同数目的对象。为了做到这一点,当选择边界时你需要计算每边有多少对象。层次包围盒是另外一种空间划分为特定集合的对象的优化。 + + * *You can ensure the partitions are balanced.* This gives not just good + performance, but *consistent* performance: if each partition has the + same number of objects, you ensure that all queries in the world will + take about the same amount of time. When you need to maintain a stable + frame rate, this consistency may be more important than raw performance. + + * *你可以保证划分是平衡的。*这不仅给了你好的性能,还给了*稳定*的性能:如果每个区域的数量保持一致,你可以保证游戏世界中的所有查询都会消耗同样数量的时间。一旦你需要固定帧率,这种一致性也许比性能本身更重要。 + + * *It's more efficient to partition an entire set of objects at once.* + When the *set* of objects affects where boundaries are, it's best to + have all of the objects up front before you partition them. This is why + these kinds of partitions are more frequently used for art and static + geometry that stays fixed during the game. + + * *依赖一组数据划分更加有效率。*当对象*组*影响了边界在哪里,最好让所有的对象在划分前都出现。这就是为什么美术和地理更多的使用这种划分。 + + * **If the partitioning is object-independent, but *hierarchy* is + object-dependent:** + + * **如果划分与对象无关,但是*层次*与对象相关:** + + One spatial partition deserves special mention because it has some of the + best characteristics of both fixed partitions and adaptable ones: quadtrees. + + 有一种数据划分需要特殊注意,因为它拥有固定划分和适应性划分两者的优点:四叉树。 + + + + A quadtree starts with the entire space as a single partition. If the number + of objects in the space exceeds some threshold, it is sliced into four + smaller squares. The *boundaries* of these squares are fixed: they always + slice space right in half. + + 一个四叉树开始时将整个空间视为单一的划分。如果空间中对象的数目超过了临界值,它将其切为四小块。这些方块的*边界*是确定的:他们总是将空间一切为二。 + + Then, for each of the four squares, we do the same process again, + recursively, until every square has a small number of objects in it. Since + we only recursively subdivide squares that have a high population, this + partitioning adapts to the set of objects, but the partitions don't *move*. + + 然后,对于四个区域中的每一个,我们递归地做相同的事情,知道每一个区域都有较少数目的对象子啊其中。由于我们只队规的分割有较高数目对象的区域,这种划分适应了对象集合,但是划分本身没有*移动*。 + + You can see the partitioning in action reading from left to right here: + + 你可以从左向右看到划分的过程: + + A quadtree. + + * *Objects can be added incrementally.* Adding a new object means finding + the right square and adding it. If that bumps that square above the + maximum count, it gets subdivided. The other objects in that square get + pushed down into the new smaller squares. This requires a little work, + but it's a *fixed* amount of effort: the number of objects you have to + move will always be less than the maximum object count. Adding a single + object can never trigger more than one subdivision. + + * *对象可以增量增加。*添加一个新对象意味着找到正确的区域然后添加进去。如果区域中的数目超过了最大限度,它就被划分。在区域总的其他对象被添加到新的小区域中。这需要一点小小的工作,但是工作的总量是*固定的*:你需要移动的对象的数目总是少于最大的对象临界值。添加一个对象从来不会引发超过一次划分。 + + Removing objects is equally simple. You remove the object from its + square and if the parent square's total count is now below the + threshold, you can collapse those subdivisions. + + 删除对象也同样简单。你从它的格子总移除对象,如果它的父格子中的计数少于临界值,你可以合并这些子划分。 + + * *Objects can be moved quickly.* This, of course, follows from the above. + "Moving" an object is just an add and a remove, and both of those are + pretty quick with quadtrees. + + * *对象移动很快。*这,当然,如上所述,“移动”一个对象只是添加和移除,两者在四叉树中都很快。 + + * *The partitions are balanced.* Since any given square will have less + than some fixed maximum number of objects, even when objects are + clustered together, you don't have single partitions with a huge pile of + objects in them. + + * *划分是平滑的。*由于任何给定的区域都有少于最大对象数量的对象,哪怕对象都堆在一起,你也不会有一块划分有很多数目的对象。 + +### Are objects only stored in the partition? + +### 对象只存储在划分之中吗? + +You can treat your spatial partition as *the* place where the objects in your +game live, or you can consider it just a secondary cache to make look-up faster +while also having another collection that directly holds the list of objects. + +你可以将你的空间划分在游戏中对象存储的*地方*,或者将其作为更快查找的二级缓存,使用另外一个集合直接包含对象的列表。 + + * **If it is the only place objects are stored:** + + * **如果他是唯一对象存储的地方:** + + * *It avoids the memory overhead and complexity of two collections.* Of + course, it's always cheaper to store something once instead of twice. + Also, if you have two collections, you have to make sure to keep them in + sync. Every time an object is created or destroyed, it has to be added + or removed from both. + + * *这避免的内存天花板和两个集合带来的复杂度。*单人,存储对象一遍总比存两遍来的轻松。同样,如果你有两个集合,你需要保证他们同步。每次对象创建或删除,它都得从两者中添加或删除。 + + * **If there is another collection for the objects:** + + * **如果有其他对象的集合:** + + * *Traversing all objects is faster.* If the objects in question are + "live" and have some processing they need to do, you may find yourself + frequently needing to visit every object regardless of its location. + Imagine if, in our earlier example, most of the cells were empty. Having + to walk the full grid of cells to find the non-empty ones can be a waste + of time. + + * *遍历所有的对象更快。*如果所有的对象都是“活的”而且有些处理他们需要做,你游戏会发现你需要频繁拜访每一个对象而不在乎它的位置。想想看,我们早先的例子中,大多数格子都是空的。访问那些空的格子是对时间的浪费。 + + A second collection that just stores the objects gives you a way to walk + all them directly. You have two data structures, one optimized for each + use case. + + 第二个存储对象的集合给了你直接遍历他们的方法。你有两个数据结构,每种为每种情况优化。 + +## See Also + +## 参见 + + * I've tried not to discuss specific spatial partitioning structures in detail + here to keep the chapter high-level (and not too long!), but your next step + from here should be to learn a few of the common structures. Despite their + scary names, they are all surprisingly straightforward. The common ones are: + + 我试图在这里不讨论特定的空间划分结构细节来保证这章高层(而且不过长!),但你的下一步应该是学习一下常见的结构。不管他们的恐怖的名字,他们都令人惊讶的直观。常见的有: + + * [Grid](http://en.wikipedia.org/wiki/Grid_(spatial_index)) + * [Quadtree](http://en.wikipedia.org/wiki/Quad_tree) + * [BSP](http://en.wikipedia.org/wiki/Binary_space_partitioning) + * [k-d tree](http://en.wikipedia.org/wiki/Kd-tree) + * [Bounding volume hierarchy](http://en.wikipedia.org/wiki/Bounding_volume_hierarchy) + + * Each of these spatial data structures basically extends an existing + well-known data structure from 1D into more dimensions. Knowing their linear + cousins will help you tell if they are a good fit for your problem: + + 每一种空间划分数据结构基本上都将一维的数据结构构建成更高维度的数据结构。知道他们的直系子孙有助于你分辨他们对你的问题是不是好的解答: + + * A grid is a persistent [bucket sort](http://en.wikipedia.org/wiki/Bucket_sort). + * BSPs, k-d trees, and bounding volume hierarchies are [binary search trees](http://en.wikipedia.org/wiki/Binary_search_tree). + * Quadtrees and octrees are [tries](http://en.wikipedia.org/wiki/Trie). + + * 网格是一个持久的桶排序。 + * BSPs,k-d trees和包围盒是线性搜索树。 + * 四叉树和八叉树是多叉树。 diff --git a/book/state.markdown b/book/state.markdown new file mode 100644 index 0000000..6c10a77 --- /dev/null +++ b/book/state.markdown @@ -0,0 +1,845 @@ +^title State +^section Design Patterns Revisited + +Confession time: I went a little overboard and packed way too much into this +chapter. It's ostensibly about the State +design pattern, but I can't talk about that and games without going into the +more fundamental concept of *finite state machines* (or "FSMs"). But then once I +went there, I figured I might as well introduce *hierarchical state machines* +and *pushdown automata*. + +忏悔时间:我有些越界将太多的东西打包到了这章中。表面上它是关于状态设计模式,但我无法仅仅只讨论它和游戏而不涉及更加基础的*有限状态机*(FSMs)。但是一旦我讲了那个,我发现我也想要介绍*层次状态机*和*下推自动机*。 + +That's a lot to cover, so to keep things as short as possible, the code samples +here leave out a few details that you'll have to fill in on your own. I hope +they're still clear enough for you to get the big picture. + +有很多要讲,我会尽可能简短,这里的示例代码留下了一些你需要自己填补 的细节。我希望他们仍然足够清晰的让你获取一份全图。 + +Don't feel sad if you've never heard of a state machine. While well known to +AI and compiler hackers, they aren't that familiar +to other programming circles. I think they should be more widely known, so I'm +going to throw them at a different kind of problem here. + +如果你从来没有听说过状态机,不要难过。虽然在AI和编译器程序员界很知名,在其他编程圈就没有那么知名了。我认为应该有更多人知道它,所以在这里我们将它运用在不同种的问题。 + + + +## We've All Been There + +## 我们都到了 + +We're working on a little side-scrolling platformer. Our job is to implement the +heroine that is the player's avatar in the game world. That means making her +respond to user input. Push the B button and she should jump. Simple enough: + +我们在完成一个卷轴平台游戏。我们的工作是实现玩家在游戏世界中操作的女英雄。这就意味着他需要对玩家的输入做出响应。按B键她应该跳跃。够简单了: + +^code spaghetti-1 + +Spot the bug? + +看到漏洞了吗? + +There's nothing to prevent "air jumping" -- keep hammering B while she's in the +air, and she will float forever. The simple fix is to +add an `isJumping_` Boolean field to `Heroine` that tracks when she's jumping, +and then do: + +这个没有东西阻止“空气跳”——在她空中时狂按B,她就会浮空。简单的修复是增加一个`isJumping_`的布尔字段给`Heroine`以追踪它跳跃的状态。然后这样做: + +^code spaghetti-2 + + + +Next, we want the heroine to duck if the player presses down while she's on the +ground and stand back up when the button is released: + +下面,当玩家按下键时,我们想要她在地上时卧倒,而松开下键的时候站起来: + +^code spaghetti-3 + +Spot the bug this time? + +这次看到了错误了吗? + +With this code, the player could: + +用这个代码,玩家可以: + +1. Press down to duck. +2. Press B to jump from a ducking position. +3. Release down while still in the air. + +1. 按下键卧倒。 +2. 按B从卧倒状态跳起。 +3. 在空中放开下键。 + +The heroine will switch to her standing graphic in the middle of the jump. Time +for another flag... + +英雄会在跳跃的半路上变成站立图片。增加另一个标识的时候到了…… + +^code spaghetti-4 + +Next, it would be cool if the heroine did a dive attack if the player presses +down in the middle of a jump: + +下面,如果玩家在跳跃途中按下下键,英雄能够做一个速降攻击就太酷了: + +^code spaghetti-5 + +Bug hunting time again. Find it? + +又是查找漏洞的时候了。找到了吗? + +We check that you can't air jump while jumping, but not while diving. Yet +another field... + +我们检查了跳跃时不能做空气跳,但是速降时没有。又是另一个字段…… + +Something is clearly wrong with our approach. Every time +we touch this handful of code, we break something. We need to add a bunch more +moves -- we haven't even added *walking* yet -- but at this rate, it will +collapse into a heap of bugs before we're done with it. + +我们的实现方法有很明显的错误。每一次我们改变这些代码时,我们就破坏了什么东西。我们需要增加更多的动作——我们甚至还没有增加*走路*呢——但是以这种速度,在我们完成之前就会遇到一堆错误。 + + + +## Finite State Machines to the Rescue + +## 有限状态机前来救援 + +In a fit of frustration, you sweep everything off your desk except a pen and +paper and start drawing a flowchart. You draw a box for each thing the heroine +can be doing: standing, jumping, ducking, and diving. When she can respond to a +button press in one of those states, you draw an arrow from that box, label it +with that button, and connect it to the state she changes to. + +在挫败之后,你把桌子一扫而空,只留下纸笔开始画流程图。你给英雄每一个能做的事情都画了一个盒子:站立,跳跃,俯卧,速降。当她能够回应在这些状态之一时,你从那个盒子画出一个箭头,标记上按钮,然后连接到她改变的状态。 + +A flowchart containing boxes for Standing, Jumping, Diving, and Ducking. Arrows for button presses and releases connect some of the boxes. + +Congratulations, you've just created a *finite state machine*. These came out of +a branch of computer science called *automata theory* whose family of data +structures also includes the famous Turing machine. FSMs are the simplest member +of that family. + +祝贺,你刚刚建好了一个*有限状态机*。它来自计算机科学的一个分支“自动理论”,那里有很多著名的数据结构,包括著名的图灵机。FSMs是其中最简单的成员。 + +The gist is: + +要点是: + + * **You have a fixed *set of states* that the machine can be in.** For our + example, that's standing, jumping, ducking, and diving. + + * **你有机器可以处于的固定数量的*状态*集合。**在我们的例子中,是站立,跳跃,俯卧和速降。 + + * **The machine can only be in *one* state at a time.** Our heroine can't be + jumping and standing simultaneously. In fact, preventing that is one reason + we're going to use an FSM. + + * **机器同时只能在*一个*状态。**我们的英雄不可能同时初一跳跃和站立。事实上,防止这一点是我们的使用FSM的理由之一。 + + * **A sequence of *inputs* or *events* is sent to the machine.** In our example, + that's the raw button presses and releases. + + * **一连串的*输入*或*事件*被发送给机器。**在我们的例子中,就是按键按下和松开。 + + * **Each state has a *set of transitions*, each associated with an input and + pointing to a state.** When an input comes in, if it matches a transition for + the current state, the machine changes to the state that transition points + to. + + * **每一个状态都有*一系列的转换*,而且与一个输入和另一个状态相关。**当一个输入进来,如果它与当前状态的某一个转换匹配,机器转为转换所指的状态。 + + For example, pressing down while standing transitions to the ducking state. Pressing down while jumping transitions to diving. If no transition is defined for an input on the current state, the input is ignored. + + 举个例子,在站立状态时,按下下键转换为俯卧状态。在跳跃时按下下键转换为速降。如果输入在当前状态没有定义转换,输入就被合适。 + +In their pure form, that's the whole banana: states, inputs, and transitions. +You can draw it out like a little flowchart. Unfortunately, the compiler doesn't +recognize our scribbles, so how do we go about *implementing* one? The Gang of +Four's State pattern is one method -- which we'll get to -- but let's start simpler. + +在他们的核心形式,这就是全部了:状态,输入,和转换。你可以用一张流程图把它画出来。不幸的是,编译器不认我们画出的,所以我们如何*实现*一个?GoF的状态模式是一个方法——我们会谈到的——但先从简单的开始。 + + + +## Enums and Switches + +## 枚举和分支 + +One problem our `Heroine` class has is some combinations of those Boolean fields +aren't valid: `isJumping_` and `isDucking_` should never both be true, for +example. When you have a handful of flags where only one is `true` at a time, +that's a hint that what you really want is an `enum`. + +我们`Heroine`累的问题来自将一些不独立的布尔字段绑在了一起:`isJumping_`和`isDucking_`,比如不会同时为真。但你有一些标识同时只有一个是·true·,有个小技巧是你真正需要的其实是`enum`。 + +In this case, that `enum` is exactly the set of states for our FSM, so let's +define that: + +在这个例子中 `enum`就是FSM的状态集合,所以让我们定义它: + +^code enum + +Instead of a bunch of flags, `Heroine` will just have one `state_` field. We +also flip the order of our branching. In the previous code, we switched on +input, *then* on state. This kept the code for handling one button press +together, but it smeared around the code for one state. We want to keep that +together, so we switch on state first. That gives us: + +不是用一堆标识,`Heroine`只有一个`state_`状态。我们同样改变了我们的分支顺序。在前面的代码中,我们先在输入上做分支,*然后*是状态。这让代码同一处理某个按键,但一个状态分布到了代码各处。我们想让它们聚在一起,所以我们先对状态做分支。这样的话: + +^code state-switch + +This seems trivial, but it's a real improvement over the previous code. We still +have some conditional branching, but we simplified the mutable state to a single field. All of the code for +handling a single state is now nicely lumped together. This is the simplest way +to implement a state machine and is fine for some uses. + +这看起来很琐碎,但是比起前面的代码是一个很大的进步了。我们仍然有一些条件分支,但是我们简化了变化的状态成一个字段。所有的处理同一个状态的代码都聚到了一起。这是实现状态机最简单的方法,对于某些使用情况也不错。 + + + +Your problem may outgrow this solution, though. Say we want to add a move where +our heroine can duck for a while to charge up and unleash a special attack. +While she's ducking, we need to track the charge time. + +但是,你的问题也许超过了这个解法的控制。假设我们想增加一个动作,当我们的英雄可以速降一段时间来充能,之后释放一次特殊攻击。当她速降时,我们需要追踪它的充能时间。 + +We add a `chargeTime_` field to `Heroine` to store how long the attack has +charged. Assume we already have an `update()` that +gets called each frame. In there, we add: + +我们为`Heroine`添加了`chargeTime_`字段来保存攻击被充能的时间。假设我们已经有一个`update()`方法每帧都会调用。在那里,我们添加: + +^code switch-update + + + +We need to reset the timer when she starts ducking, so we modify +`handleInput()`: + +我们需要在她开始速降的时候重置计时器,所以我们修改`handleInput()`: + +^code state-switch-reset + +All in all, to add this charge attack, we had to modify two methods and add a +`chargeTime_` field onto `Heroine` even though it's only meaningful while in +the ducking state. What we'd prefer is to have all of that code and data nicely +wrapped up in one place. The Gang of Four has us covered. + +总而言之,为了增加这个充能攻击,我们需要修改两个方法,添加一个`chargeTime_`字段到`Heroine`,哪怕它只在速降时有意义。我们更喜欢的是让所有的代码和数据都待在同一个地方。GoF完成了这个。 + +## The State Pattern + +## 状态模式 + +For people deeply into the object-oriented mindset, every conditional branch is an opportunity to use dynamic dispatch (in other +words a virtual method call in C++). I think you can go too far down that +rabbit hole. Sometimes an `if` is all you need. + +对于那些深深沉浸在面向对象思维方式的人,每一个分支都是使用动态分配(在C++中被叫做虚方法调用)。我觉得那就在兔子洞里挖得太深了。有时候一个`if`就能满足你的需要了。 + + + +But in our example, we've reached a tipping point where something +object-oriented is a better fit. That gets us to the State pattern. In the words +of the Gang of Four: + +但是在我们的例子中,我们抵达了面向对象是更好的方式的转折点。这带领我们走向状态模式。在GoF中这样描述: + +> Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. + +> 允许一个对象当他的内部状态改变时改变其行为。对象好像改变了类一样。 + +That doesn't tell us much. Heck, our `switch` does that. The concrete pattern +they describe looks like this when applied to our heroine: + +这可没太多帮助。我们的`switch`也完成了这一点。他们描述的东西应用在我们的英雄身上是这样的: + +### A state interface + +### 一个状态接口 + +First, we define an interface for the state. Every bit of behavior that is +state-dependent -- every place we had a `switch` before -- becomes a virtual +method in that interface. For us, that's `handleInput()` and `update()`: + +首先,我们为状态定义一个接口。状态相关的行为——我们之前用`switch`的每一处——成为了接口中的一个虚方法。对于我们来说,那是`handleInput()`和`update()`: + +^code heroine-state + +### Classes for each state + +### 为每个状态写类 + +For each state, we define a class that implements the interface. Its methods +define the heroine's behavior when in that state. In other words, take each `case` +from the earlier `switch` statements and move them into their state's class. For example: + +对于每一个状态,我们定义一个类实现了接口。它的方法定义了英雄在那个状态的行为。换言之,取出之前的`switch`中每一个`case`然后将他们移动到状态类中。举个例子: + +^code ducking-state + +Note that we also moved `chargeTime_` out of `Heroine` and into the `DuckingState` +class. This is great -- that piece of data is only meaningful while in that state, +and now our object model reflects that explicitly. + +注意我们也将`chargeTime_`移出了`Heroine`然后放到了`DuckingState`类中。这很好——那部分数据只在这个状态有用,现在我们的对象模型直接反射了这一点。 + +### Delegate to the state + +### 状态委托 + +Next, we give the `Heroine` a pointer to her current state, lose each big `switch`, and delegate to the state instead: + +然后,我们给`Heroine`一个指针指向她的当前状态,放弃巨大的`switch`,转而委托给状态。 + + + +^code gof-heroine + +In order to "change state", we just need to assign `state_` to point to a +different `HeroineState` object. That's the State pattern in its entirety. + + + +## Where Are the State Objects? + +## 状态对象在哪里? + +I did gloss over one bit here. To change states, we need to assign `state_` to +point to the new one, but where does that object come from? With our `enum` +implementation, that was a no-brainer -- `enum` values are primitives like +numbers. But now our states are classes, which means we need an actual instance +to point to. There are two common answers to this: + +我这里掩盖了一点。为了改变状态,我们需要声明`state_`指向新的,但是那个对象从哪里来呢?通过我们的`enum`实现,这都不用过脑子——`enum`实际上就像数字一样。但是现在状态时类了,意味着我们需要指向实例。通常这有两种回答: + +### Static states + +### 静态状态 + +If the state object doesn't have any other fields, then +the only data it stores is a pointer to the internal virtual method table so +that its methods can be called. In that case, there's no reason to ever have +more than one instance of it. Every instance would be identical anyway. + +如果状态对象没有其他字段,那么它存储的多有数据就是一个指针指向虚方法表,这样他的方法可以被调用。在这种情况下,没理由产生超过一个实例。毕竟每一个实例都完全一样。 + + + +In that case, you can make a single *static* instance. Even if you have a +bunch of FSMs all going at the same time in that same state, they can all point +to the same instance since it has nothing +machine-specific about it. + +在那种情况下,你可以制造一个*静态*实例。哪怕你有一堆FSM在同时在同一状态上运行,他们都能接触到同样的实例,因为没有机器特定的部分。 + + + +*Where* you put that static instance is up to you. Find a place that makes +sense. For no particular reason, let's put ours inside the base state class: + +你在*那里放置静态实例取决于你。找一个合理的地方。没有特殊的原因,把我们的放在状态基类中。 + +^code heroine-static-states + +Each of those static fields is the one instance of that state that the game +uses. To make the heroine jump, the standing state would do something like: + +每一个静态字段都是游戏使用的状态的一个实例。为了让英雄跳跃,站立状态会做些这样的事情: + +^code jump + +### Instantiated states + +### 实例化状态 + +Sometimes, though, this doesn't fly. A static state won't work for the ducking +state. It has a `chargeTime_` field, and that's specific to the heroine that +happens to be ducking. This may coincidentally work in our game if there's only +one heroine, but if we try to add two-player co-op and have two heroines on +screen at the same time, we'll have problems. + +有时,没那么容易。一个静态状态对俯卧不起作用。他有一个`chargeTime_`字段,与那个正在俯卧的英雄特定相关。在我们的游戏中如果只有一个英雄也能工作,但是如果我们要添加双人合作并同时在平面上要有两个英雄,我们就遇到麻烦了。 + +In that case, we have to create a state +object when we transition to it. This lets each FSM have its own instance of the state. Of course, if we're allocating a *new* state, that means we need to free the *current* one. We have to be careful here, since the code that's triggering the change is in a method in the current state. We don't want to delete `this` out from under ourselves. + +在那种情况下,转换时需要创建一个状态对象。这让每一个FSM拥有它自己的状态实例。单人,如果我们分配一个*新*状态,那意味着我们需要释放*当前的*。我们在这里要小心,由于触发变化的代码是当前状态中的一个方法。我们不想自己删除`this`。 + +Instead, we'll allow `handleInput()` in `HeroineState` to optionally return a new state. When it does, `Heroine` will delete the old one and swap in the new one, like so: + +相反,我们允许`HeroineState`中的`handleInput()`随机的返回一个新状态。如果它那么做了,`Heroine`会删除旧的,然后换成新的,就像这样: + +^code swap-instance + +That way, we don't delete the previous state until we've returned from its method. Now, the standing state can transition to ducking by creating a new instance: + +这样,我们直到我们从之前的状态返回,我们才需要删除它。现在,站立状态可以通过创建一个新实例转换为俯卧状态。 + +^code duck + +When I can, I prefer to use static states since they don't burn memory and CPU cycles allocating objects +each state change. For states that are more, uh, *stateful*, though, this is the +way to go. + +如果可以,我倾向于使用静态状态,因为他们不会在每个状态转换时消耗太多的内存和CPU。但是,对于那些有更多的状态,额,*多状态的*,这是一条可选的路。 + + + +## Enter and Exit Actions + +## 进入和退出行为 + +The goal of the State pattern is to encapsulate all of the behavior and data for +one state in a single class. We're partway there, but we still have +some loose ends. + +状态模式的目标是将一个状态的行为和数据封装到单一类中。我们部分的完成了,但是还有一些未了之事。 + +When the heroine changes state, we also switch her sprite. Right now, that code is owned by the state she's switching *from*. When she goes from ducking to standing, the ducking state sets her image: + +当英雄改变状态,我们也改变她的图片。现在,那部分代码在她转换*前*的状态上。当她从俯卧转为站立,俯卧状态设置了她的图片: + +^code enter-standing-before + +What we really want is each state to control its own graphics. We can +handle that by giving the state an *entry action*: + +我们想做的是每个状态控制她自己的图像。我们可以给状态一个*行为入口*来处理它。 + +^code standing-with-enter + +Back in `Heroine`, we modify the code for handling state changes to call that +on the new state: + +在`Heroine`中,我们修改了处理状态改变的代码,在新的状态上上调用: + +^code change-state + +This lets us simplify the ducking code to: + +这让我们将俯卧代码简化为: + +^code enter-standing + +All it does is switch to standing and the standing state takes care of +the graphics. Now our states really are encapsulated. One particularly nice thing +about entry actions is that they run when you enter the state regardless of +which state you're coming *from*. + +他做的所有事情就是转换为站立,然后站立状态控制图形。现在我们的状态真的封装了。关于行为入口特别好的事情就是当你进入状态的时候,不必关系你是从哪个状态转换*来的*。 + +Most real-world state graphs have multiple transitions into the same state. For +example, our heroine will also end up standing after she lands a jump or dive. That means we would end up duplicating some code everywhere that transition +occurs. Entry actions give us a place to consolidate that. + +大多数现实世界的状态图形都有从多个转换进入同一个状态。举个例子,我们的英雄在它跳跃或速降后追踪进入站立状态。这意味着我们在转换发生的最后到处复制相同的代码。行为入口给了我们一个好地方来巩固那一点。 + +We can, of course, also extend this to support an *exit action*. This is just a +method we call on the state we're *leaving* right before we switch to the new +state. + +我们能,当然,扩展来支持一个*行为出口*。这只是在我们*离开*现有状态转换到新状态之前调用的一个方法。 + +## What's the Catch? + +## 有什么收获? + +I've spent all this time selling you on FSMs, and now I'm going to pull the rug +out from under you. Everything I've said so far is true, and FSMs are a good fit +for some problems. But their greatest virtue is also their greatest flaw. + +我花了这么长时间向您推销FSMs,现在我要从你脚下抽走地毯了。我讲的到现在都是真的,而FSM能很好解决一些问题。但是他们最大的优点也是他们最大的缺点。 + +State machines help you untangle hairy code by enforcing a very constrained structure on it. All you've got is a fixed set +of states, a single current state, and some hardcoded transitions. + +状态机通过使用一个有约束的结构来理清杂乱的代码。你得到的是一些固定状态,单一的当前状态,和一些硬编码的转换。 + + + +If you try using a state machine for something more complex like game AI, you +will slam face-first into the limitations of that model. Thankfully, our +forebears have found ways to dodge some of those barriers. I'll close this +chapter out by walking you through a couple of them. + +如果你需要为更复杂的东西比如游戏AI使用状态机,你的脸会撞到这个模型的限制上。感谢上天,我们的前辈找到了一些方法来闪避这些障碍。我会浏览他们中的一些来结束这一章。 + +## Concurrent State Machines + +## 并发状态机 + +We've decided to give our heroine the ability to carry a gun. When she's packing +heat, she can still do everything she could before: run, jump, duck, etc. But +she also needs to be able to fire her weapon while doing it. + +我们决定给我们的英雄带枪的能力。当她拿着枪的时候,她还是能做她之前能做的任何事情:跑,跳,速降,等等。但是她也需要在做这些的同时开火。 + +If we want to stick to the confines of an FSM, we have to *double* the number of +states we have. For each existing state, we'll need another one for doing the +same thing while she's armed: standing, standing with gun, jumping, jumping with +gun, you get the idea. + +如果我们绑定在FSM上,我们需要*翻倍*我们有的状态。对于每一个现存状态,我们需要另一个她武装时候的状态:站立,持枪站立,跳跃,持枪跳跃,你知道我的意思。 + +Add a couple of more weapons and the number of states explodes combinatorially. +Not only is it a huge number of states, it's a huge amount of redundancy: the +unarmed and armed states are almost identical except for the little bit of code +to handle firing. + +多加几种武器,状态就会指数爆炸。这不但有大量的状态,这也是大量的冗余:武装和非武装状态集合是完全一致的,只是多了一点负责设计的代码。 + +The problem is that we've jammed two pieces of +state -- what she's *doing* and what she's *carrying* -- into a single machine. +To model all possible combinations, we would need a state for each *pair*. The +fix is obvious: have two separate state machines. + +问题在于我们绑定了两种状态——她*做的*和她*携带的*——到了一个状态机上。为了处理所有可能的结合,我们需要为每一*对*写一个状态。修复很明显:使用两个单独的状态机。 + + + +We keep our original state machine for what she's doing and leave it alone. Then +we define a separate state machine for what she's carrying. `Heroine` will have +*two* "state" references, one for each, like: + + +我们保留原来的记录她在做什么的状态机,不再管它。然后我们定义一个她携带了什么的单独状态机。`Heroine`将会有*两个*“状态”引用,每个对应一个状态机,就像这样: + + +^code two-states + + + +When the heroine delegates inputs to the states, she hands it to both of them: + +当英雄把输入委托给了状态,她将其交给两个: + + + +^code handle-two-inputs + + + +Each state machine can then respond to inputs, spawn behavior, and change its +state independently of the other machine. When the two sets of states are mostly +unrelated, this works well. + +每一个状态机之后都能响应输入,发生行为,独立于另外其他机器改变状态。当两个状态集合几乎没有联系的时候,这能工作的不错。 + +In practice, you'll find a few cases where the states do interact. For example, +maybe she can't fire while jumping, or maybe she can't do a dive attack if she's +armed. To handle that, in the code for one state, you'll probably just do some +crude `if` tests on the *other* machine's state to coordinate them. It's not the most +elegant solution, but it gets the job done. + +在实践中,你会发现有些时候状态子啊交互。举个例子,也许她在跳跃时不能开火,或者他在武装时不能速降攻击。为了处理这个,在一个状态的代码中,你也许会做一些粗糙的`if`测试*其他*机器的状态来与他们协同,这不是最优雅的解决方案,但这可以搞定工作。 + +## Hierarchical State Machines + +## 分层状态机 + +After fleshing out our heroine's behavior some more, she'll likely have a bunch +of similar states. For example, she may have standing, walking, running, and +sliding states. In any of those, pressing B jumps and pressing down ducks. + +在充实我们英雄的行为,她可能会有更多相似的状态。举个例子,他也许有站立,行走,奔跑,和滑铲状态。在他们中的每一个,按B跳按下蹲。 + +With a simple state machine implementation, we have to duplicate that code in +each of those states. It would be better if we could implement that once and +reuse it across all of the states. + +通过一个简单的状态机实现,我们子啊这些状态中的每一个都重复了代码。如果我们能够实现一次然后在多个状态间重用就好了。 + +If this was just object-oriented code instead of a state machine, one way to +share code across those states would be using inheritance. We could define a class for an "on +ground" state that handles jumping and ducking. Standing, walking, running, and +sliding would then inherit from that and add their own additional behavior. + +如果这是一个面向对象的代码而不是一个状态机,一种在这些状态间分享代码的方式是通过继承。我们可以为“在地面上”定义一个类处理跳跃和速降。站立,行走,奔跑和滑铲都会从它继承,然后增加他们的附加行为。 + + + +It turns out, this is a common structure called a *hierarchical state machine*. +A state can have a *superstate* (making itself a *substate*). When an event +comes in, if the substate doesn't handle it, it rolls up the chain of +superstates. In other words, it works just like overriding inherited methods. + +你会发现,这是一个通用结构被称为*分层状态机*。一个状态可以有*父状态*(这让他变为*子状态*)。当一个事件进来,如果子状态没有处理,它就会交给链上的父状态。换言之,它像重载的继承方法那样运作。 + +In fact, if we're using the State pattern to implement our FSM, we can +use class inheritance to implement the hierarchy. Define a base class for the +superstate: + +事实上,如果我们使用状态模式实现FSM,我们可以使用继承来实现层次。定义一个基类作为父状态: + +^code on-ground + +And then each substate inherits it: + +每个子状态继承它: + +^code duck-on-ground + +This isn't the only way to implement the hierarchy, of course. If you aren't +using the Gang of Four's State pattern, this won't work. Instead, you can model +the current state's chain of superstates explicitly using a *stack* of states +instead of a single state in the main class. + +这当然不是唯一实现层次的方法。如果你没有使用GoF的状态模式,这不会有用。相反,你可以显式的使用状态*栈*而不是单一的状态来表示当前状态的父状态链。 + +The current state is the one on the top of the stack, under that is its +immediate superstate, and then *that* state's superstate and so on. When you +dish out some state-specific behavior, you start at the top of the stack and +walk down until one of the states handles it. (If none do, you ignore it.) + +当前状态时栈顶的状态,在他下面是它的直接父状态,然后是*那个*状态的父状态,一直下去。但你发现了一些特定状态的行为,你从栈的顶端开始,然后向下移动知道某一个状态处理了它。(如果没有,你忽视它。) + +## Pushdown Automata + +## 下推自动机 + +There's another common extension to finite state machines that also uses a stack +of states. Confusingly, the stack represents something entirely different, and +is used to solve a different problem. + +还有一种有限自动机的扩展也用了状态栈。令人困惑的是,栈表示的是完全不同的事物,被用作解决不同的问题。 + +The problem is that finite state machines have no concept of *history*. You know +what state you *are* in, but have no memory of what state you *were* in. There's +no easy way to go back to a previous state. + +问题是有限自动机没有任何*历史*的概念。你知道你*在*什么状态中,但是不记得你*曾在*什么状态。这没有简单的办法回到上一状态。 + +Here's an example: Earlier, we let our fearless heroine arm herself to the +teeth. When she fires her gun, we need a new state that plays the firing +animation and spawns the bullet and any visual effects. So we slap together a +`FiringState` and make all of the states that she can +fire from transition into that when the fire button is pressed. + +这里是个例子:早先,我们让我们的无畏英雄武装到了牙齿。当她开火,我们需要一个新状态播放开火动画,产生子弹和视觉效果。所以我们拼凑了一个`FiringState`,让所有的状态都能在开火按钮按下的时候跳转过去。 + + + +The tricky part is what state she transitions to *after* firing. She can pop off +a round while standing, running, jumping, and ducking. When the firing sequence +is complete, she should transition back to what she was doing before. + +有技巧的部分是它开火*后*转换到的状态。她可以在站立,奔跑,跳跃,速降时开火。当开火序列完成了,它应该转换为她之前的状态。 + +If we're sticking with a vanilla FSM, we've already forgotten what state she was +in. To keep track of it, we'd have to define a slew of nearly identical states +-- firing while standing, firing while running, firing while jumping, and so on +-- just so that each one can have a hardcoded transition that goes back to the +right state when it's done. + +如果我们固执于纯粹的FSM,我们已经忘了她所处的状态。为了继续追踪它,我们定义了很多差不多完全一样的类——站立开火,跑步开火,跳跃开火,诸如此类——每一个都有硬编码的转换回到她刚刚在做的事情。 + +What we'd really like is a way to *store* the state she was in before firing and +then *recall* it later. Again, automata theory is here to help. The relevant +data structure is called a [*pushdown +automaton*](http://en.wikipedia.org/wiki/Pushdown_automaton). + +我们真正喜欢的是*存储*它在开火前所处的状态然后等会*回想*起来。又一次,自动理论来帮忙了,相关的数据结构被称为下推自动机。 + +Where a finite state machine has a *single* pointer to a state, a pushdown +automaton has a *stack* of them. In an FSM, transitioning to a new state +*replaces* the previous one. A pushdown automaton lets you do that, but it also +gives you two additional operations: + +当有限自动机有*一个*指针指向状态,一个下推自动机有*一栈*。在FSM中,转换到新状态*代替*了之前的那个。下推状态机不仅能让你完成那个,还能给你两个额外操作: + + 1. You can *push* a new state onto the stack. The "current" state is always the + one on top of the stack, so this transitions to the new state. But it leaves + the previous state directly under it on the stack instead of discarding it. + + 1. 你可以将一个新状态*推入*栈中。“当前的”状态总是在栈顶,所以这会转到新的状态。但它让之前的状态待在栈中而不是销毁它。 + + 2. You can *pop* the topmost state off the stack. That state is discarded, and + the state under it becomes the new current state. + + 2. 你可以*弹出*最上面的状态。这个状态会被销毁,在它下面的状态成为新的状态。 + +The stack for a pushdown automaton. First it just contains a Standing state. A Firing state is pushed on top, then popped back off when done. + +This is just what we need for firing. We create a *single* firing state. When +the fire button is pressed while in any other state, we *push* the firing state +onto the stack. When the firing animation is done, we *pop* that state off, and +the pushdown automaton automatically transitions us right back to the state we +were in before. + +这正是我们开火需要的。我们创建*单一*开火状态。当开火按钮在其他状态按下时,我们*推入*开火状态。当开火动画结束,我们*弹出*状态,然后下推自动机自动转回我们之前的状态。 + +## So How Useful Are They? + +## 所以他们有多有用呢? + +Even with those common extensions to state machines, they are still pretty +limited. The trend these days in game AI is more toward exciting things like +*[behavior trees][]* and *[planning systems][]*. If complex AI is what you're interested in, +all this chapter has done is whet your appetite. You'll want to read other books +to satisfy it. + +即使状态机有这些常见的扩展,他们还是很受限制。这让今日游戏AI移向了更加激动人心的事情比如*行为树*和*计划系统*。如果复制的AI是你关注的,这一整章只是勾起了你的食欲。你需要阅读其他书来满足它。 + +[behavior trees]: http://web.archive.org/web/20140402204854/http://www.altdevblogaday.com/2011/02/24/introduction-to-behavior-trees/ +[planning systems]: http://web.media.mit.edu/~jorkin/goap.html + +This doesn't mean finite state machines, pushdown automata, and other simple +systems aren't useful. They're a good modeling tool for certain kinds of +problems. Finite state machines are useful when: + +这不意味着有限自动机,下推自动机,和其他简单的系统无用。他们是特定问题的好工具。有限自动机在以下情况有用: + + * You have an entity whose behavior changes based on some internal state. + + * 你有个实体它的行为基于一些内在状态。 + + * That state can be rigidly divided into one of a relatively small number of + distinct options. + + * 这个状态可以被有效的分割为一系列互不相干的选项。 + + * The entity responds to a series of inputs or events over time. + + * 实体随着时间响应一系列输入或事件。 + +In games, they are most known for being used in AI, but they are also common in +implementations of user input handling, navigating menu screens, parsing text, +network protocols, and other asynchronous behavior. + +在游戏中,他们因在AI中使用而闻名,但是它常见子啊其他实现比如处理玩家输入,导航菜单界面,分析文字,网络协议以及其他异步行为。 diff --git a/book/subclass-sandbox.markdown b/book/subclass-sandbox.markdown new file mode 100644 index 0000000..248a553 --- /dev/null +++ b/book/subclass-sandbox.markdown @@ -0,0 +1,667 @@ +^title Subclass Sandbox +^section Behavioral Patterns + +## Intent + +## 意图 + +*Define behavior in a subclass using a set of operations provided by its +base class.* + +*用一系列由基类定义的操作定义子类中的行为。* + +## Motivation + +## 动机 + +Every kid has dreamed of being a superhero, but unfortunately, cosmic rays are +in short supply here on Earth. Games that let you pretend to be a superhero are +the closest approximation. Because our game designers have never learned to say, +"no", *our* superhero game aims to feature dozens, if not hundreds, of +different superpowers that heroes may choose from. + +每个孩子都梦想过变成超级英雄,但是不幸的是,高能射线在地球上很短缺。游戏是让你假装是超级英雄的最简单的方法。因为我们的游戏设计者从来没有学会说“不”,*我们的*超级英雄游戏面向成打,如果不是成百的,不同的超级能力可以选择。 + +Our plan is that we'll have a `Superpower` base class. Then, we'll have a derived class that implements each superpower. We'll divvy up +the design doc among our team of programmers and get coding. When we're done, +we'll have a hundred superpower classes. + +我们的集合是有一个`Superpower`基类。然后我们有它推导出各种超级能力的实现。我们在我们的程序员队伍中分发设计文档,然后开始变成,当我们完成后,我们就会有上百的超级能力类。 + + + +We want to immerse our players in a world teeming with variety. Whatever power +they dreamed up when they were a kid, we want in our game. That means these +superpower subclasses will be able to do just about everything: play sounds, +spawn visual effects, interact with AI, create and destroy other game entities, +and mess with physics. There's no corner of the codebase that they won't touch. + +我们想让玩家处于充满变化的游戏世界中。无论他们在孩子时想象过什么能力,我们都要在游戏中表现。这就意味着这些超级能力子类需要做任何事情:播放声音,产生视觉此熬过,与AI交互,创建和销毁其他游戏实体,与物理打交道。没有哪处嗲吗是他们不能碰到的。 + +Let's say we unleash our team and get them writing superpower classes. What's +going to happen? + +假设我们让团队信马由缰的写超级能力类。会发生什么? + + * *There will be lots of redundant code.* While the different powers will be + wildly varied, we can still expect plenty of overlap. Many of them will + spawn visual effects and play sounds in the same way. A freeze ray, heat + ray, and Dijon mustard ray are all pretty similar when you get down to it. + If the people implementing those don't coordinate, there's going to be a lot + of duplicate code and effort. + + * *会有很多冗余代码。*当有非常不同的各种能力,我们可以预期有很多重叠。他们很多都会用相同的方式发出视觉效果并播放声音。当你坐下来看看,冷冻光线,加热光线,芥末酱光线都很相似。如果人们实现这些的时候没有协同,那就会有很多冗余的代码和付出。 + + * *Every part of the game engine will get coupled to these classes.* Without + knowing better, people will write code that calls into subsystems that were + never meant to be tied directly to the superpower classes. If our renderer + is organized into several nice neat layers, only one of which is intended to + be used by code outside of the graphics engine, we can bet that we'll end up + with superpower code that pokes into every one of them. + + * *游戏引擎中的每一部分都会与这些类耦合。*没有其他信息的话,任何会写直接调用子系统的代码,它们从来没打算直接与超级能力类绑定。如果我们的渲染系统被好好组织成多个层次,只有其中之一能被外部的图形引擎使用,我们可以大度我的超级能力代码与它们中的每一个回到它们中的每一个。 + + * *When these outside systems need to change, odds are good some random + superpower code will get broken.* Once we have different superpower classes + coupling themselves to various and sundry parts of the game engine, it's + inevitable that changes to those systems will impact the power classes. + That's no fun because your graphics, audio, and UI programmers probably + don't want to also have to be gameplay programmers *too*. + + * *当外部代码需要改变是,有很大几率一些随机超能力代码会损坏。*一旦我们有了不同的超能力类绑定到多种杂乱的游戏引擎部分,改变那些期限必然影响能力类。这没什么意思,因为你的图形,音频,UI程序员很可能不想*也*成为玩法程序员。 + + * *It's hard to define invariants that all superpowers obey.* Let's say we + want to make sure that all audio played by our powers gets properly queued + and prioritized. There's no easy way to do that if our hundred classes are + all directly calling into the sound engine on their own. + + * *很难定义所有超能力遵守的不变量。*假设我们想保证我们能力播放的所有音频都正确的排序和优先化。如果我们几百个类都直接自己调用音频引擎就没什么好办法来完成那一点。 + +What we want is to give each of the gameplay programmers who is implementing a +superpower a set of primitives they can play with. You want your power to play a +sound? Here's your `playSound()` function. You want particles? Here's +`spawnParticles()`. We'll make sure these operations cover everything you need +to do so that you don't need to `#include` random headers and nose your way into +the rest of the codebase. + +我们想要的是给么一个实现超能力的玩法程序员一系列他们可以使用的基本单元。你想要播放声音?这是你的`playSound()`函数。你想要粒子?这是`spawnParticles()`。我们保证了这些操作覆盖了你需要做的事情,所以你不需要`#include`随机的头文件,干扰到代码库的其他部分。 + +We do this by making these operations *protected methods of the* `Superpower` +*base class*. Putting them in the base class gives every power subclass direct, +easy access to the methods. Making them protected (and likely non-virtual) communicates +that they exist specifically to be *called* by subclasses. + +我们通过定义这些操作为`Superpower`*基类*的*保护方法*。将它们放在基类给了每个能力子类直接,方便的途径获取方法。让它们保护(有些像非虚)暗示了他们存在就是为了被子类*调用*。 + +Once we have these toys to play with, we need a place to use them. For that, +we'll define a *sandbox method*, an abstract protected method that subclasses +must implement. Given those, to implement a new kind of power, you: + +一旦我们有了这些东西来玩耍,我们需要一个地方使用他们。为了那个,我们定义一个*沙箱方法*,一个子类必须实现的抽象的保护方法。获得了那些,要实现一种新的能力,你需要: + +1. Create a new class that inherits from `Superpower`. + +2. Override `activate()`, the sandbox method. + +3. Implement the body of that by calling the protected methods that + `Superpower` provides. + +1. 创建一个从`Superpower`继承的新类。 + +2. 重载`activate()`,沙箱方法。 + +3. 通过调用`Superpower`提供的保护方法实现主体。 + +We can fix our redundant code problem now by making those provided operations as +high-level as possible. When we see code that's duplicated between lots of the +subclasses, we can always roll it up into `Superpower` as a new operation that +they can all use. + +我们现在可以使用这些尽可能高层次的操作来解决我们的冗余代码问题了。当我们看到代码在多个子类间重复,我们总可以将其打包到`Superpower`中,作为他们都可以使用的新操作。 + +We've addressed our coupling problem by constraining the coupling to one place. +`Superpower` itself will end up coupled to the different game systems, but our +hundred derived classes will not. Instead, they are *only* coupled to their base +class. When one of those game systems changes, modification to `Superpower` may +be necessary, but dozens of subclasses shouldn't have to be touched. + +我们通过将耦合约束到一个地方解决了我们的耦合问题。`Superpower`最终与不同的系统耦合,但是我们继承的几百个类不会。相反,他们*唯一*耦合他们的基类。当其中一个游戏系统改变时,修改`Superpower`也许是必须的,但是成打的子类不需要修改。 + +This pattern leads to an architecture where you have a shallow but wide class +hierarchy. Your inheritance chains aren't *deep*, but +there are a *lot* of classes that hang off `Superpower`. By having a single +class with a lot of direct subclasses, we have a point of leverage in our +codebase. Time and love that we put into `Superpower` can benefit a wide set of +classes in the game. + +这个模式指向浅层但是广泛的类层次。你的继承链不*深*,但是有*很多*类挂在`Superpower`上。通过有很多直接子类的基类,我们在代码库中创造了一个支撑点。我们投入到`Superpower`的时间和爱可以让游戏中广泛的类收益。 + + + +## The Pattern + +## 模式 + +A **base class** defines an abstract **sandbox method** and several **provided +operations**. Marking them protected makes it clear that they are for use by +derived classes. Each derived **sandboxed subclass** implements the sandbox +method using the provided operations. + +一个**基类**顶一个一个抽象的**沙箱方法**和几个**提供的操作**。让他们处于保护表面他们只为继承类所使用。每一个推导出的**沙箱子类**用提供的操作实现了沙箱方法。 + +## When to Use It + +## 何时使用 + +The Subclass Sandbox pattern is a very simple, common pattern lurking in lots of codebases, even outside +of games. If you have a non-virtual protected method laying around, you're +probably already using something like this. Subclass Sandbox is a good fit +when: + +子类沙箱模式是潜伏在代码库中简单常用的模式,哪怕是在游戏之外的地方。乳沟你有一个非虚方法的保护方法,你可能已经在用某些类似的东西了。沙箱方法在以下情况适用: + + * You have a base class with a number of derived classes. + + * 你有一个能推导很多子类的基类。 + + * The base class is able to provide all of the operations that a derived class + may need to perform. + + * 基类可以提供推导类需要的所有操作。 + + * There is behavioral overlap in the subclasses and you want to make it easier + to share code between them. + + * 在在子类总有行为重合,你想要更容易的在他们间分享代码。 + + * You want to minimize coupling between those derived classes and the rest of + the program. + + * 你想要最小化推导类和程序的其他部分的耦合。 + +## Keep in Mind + +## 记住 + +"Inheritance" is a bad word in many programming circles these days, and one +reason is that base classes tend to accrete more and more code. This pattern is +particularly susceptible to that. + +“继承”现在在很多编程圈子是一个坏词,一个原因是基类趋向于增加越来越多的代码。这个模式特别容易被感染。 + +Since subclasses go through their base class to reach the rest of the game, the +base class ends up coupled to every system *any* derived class needs to talk to. +Of course, the subclasses are also intimately tied to their base class. That +spiderweb of coupling makes it very hard to change the base class without +breaking something -- you've got the [brittle base class problem][]. + +由于子类通过他们的基类接触游戏的剩余部分,基类最后和*每个*推导类需要的所有系统耦合。当然,子类也紧密的与基类相绑定。这种蛛网耦合让你很难在不破坏什么的情况下改变基类——你获得了易碎基类问题。 + +The flip side of the coin is that since most of your coupling has been pushed up +to the base class, the derived classes are now much more cleanly separated from +the rest of the world. Ideally, most of your behavior will be in those +subclasses. That means much of your codebase is isolated and easier to maintain. + +硬币的另一面是由于你耦合的大部分都被推到了基类,推导类现在与世界的其他部分分离。理想的,你大多数的行为都在哪些子类中。这意味着你的代码库大部分是孤立的,很容易管理。 + +Still, if you find this pattern is turning your base class into a giant bowl of +code stew, consider pulling some of the provided operations out into separate +classes that the base class can dole out responsibility to. The Component pattern can help here. + +如果你发现这个模式正把你的基类变成一锅代码糊糊,考虑将它提供的一些操作提出放入分离的类中,这样基类可以分散它的责任。组件模式可以帮得到忙。 + +[brittle base class problem]: http://en.wikipedia.org/wiki/Fragile_base_class + +## Sample Code + +## 示例代码 + +Because this is such a simple pattern, there isn't much to the sample code. That +doesn't mean it isn't useful -- the pattern is about the *intent*, not the +complexity of its implementation. + +因为则是一个如此简单的模式,示例代码中没有太多东西。这不是说它没有用——这个模式是关于“意图”,而不是它实现复杂度。 + +We'll start with our `Superpower` base class: + +我们从`Superpower`基类开始: + +^code 1 + +The `activate()` method is the sandbox method. Since it is virtual and abstract, +subclasses *must* override it. This makes it clear to someone creating a power +subclass where their work has to go. + +`activate()`方法是沙箱方法。由于它是虚和抽象的,子类*必须*重载它。这让那些需要创建子类的人知道他们需要在哪里做工作。 + +The other protected methods, `move()`, `playSound()`, and `spawnParticles()`, are +the provided operations. These are what the subclasses will call in their +implementation of `activate()`. + +其他的保护方法,`move()`,`playSound()`,和`spawnParticles()`都是提供的操作。它们是子类在实现`activate()`要调用的。 + +We didn't implement the provided operations in this example, but an actual game +would have real code there. Those methods are where `Superpower` gets coupled to +other systems in the game -- `move()` may call into physics code, `playSound()` +will talk to the audio engine, etc. Since this is all in the *implementation* of +the base class, it keeps that coupling encapsulated within `Superpower` itself. + +在这个例子中,我们没有实现提供的操作,但一个真正的游戏会在那里有真正的代码。那些代码是`Superpower`与游戏中其他部分的耦合——`move()`也许调用物理代码,`playSound()`会与音频引擎交互,等等。由于这都在基类的*实现*中,保证了耦合封闭在`Superpower`中。 + +OK, now let's get our radioactive spiders out and create a power. Here's one: + +好了,让我们的放射蜘蛛侠处理创建一个能力。这里是一个: + + + +^code 2 + + + +This power springs the superhero into the air, playing an appropriate sound and +kicking up a little cloud of dust. If all of the superpowers were this simple -- +just a combination of sound, particle effect, and motion -- then we wouldn't +need this pattern at all. Instead, `Superpower` could have a baked-in +implementation of `activate()` that accesses fields for the sound ID, particle +type, and movement. But that only works when every power essentially works the +same way with only some differences in data. Let's elaborate on it a bit: + +这种能力将超级英雄弹射到天空,播放一阵适合的声音,踢起一堆尘土。如果所有的超能力都这样简单——声音,粒子效果,动作的组合——那么我就根本不想要这个模式了。相反,`Superpower`有内置的`activate()`获得了声音ID,粒子类型和运动的字段。但是那样只在所有能力都以相同方式工作,只有一些数据上的不同的时候可行。让我们精细一些: + +^code 3 + +Here, we've added a couple of methods to get the hero's position. Our +`SkyLaunch` subclass can now use those: + +这里我们增加了些方法获取英雄的位置。我们的`SkyLaunch`现在可以使用那些了: + +^code 4 + +Since we have access to some state, now our sandbox method can do actual, +interesting control flow. Here, it's still just a couple of simple `if` +statements, but you can do anything you want. By having +the sandbox method be an actual full-fledged method that contains arbitrary +code, the sky's the limit. + +由于我们接触了些状态,现在我们的沙箱方法可以做实际有趣的控制流了。这,还只有几个简单的`if`声明,但你可以做任何你想的东西。使用完全成熟的沙箱方法,天高任鸟飞了。 + + + +## Design Decisions + +## 设计决策 + +As you can see, Subclass Sandbox is a fairly "soft" pattern. It describes a basic idea, but +it doesn't have a lot of detailed mechanics. That means you'll be making some +interesting choices each time you apply it. Here are some questions to consider. + +如你所见,子类沙箱是一个“软”模式。它报数了一个基本思路,但是没有很多细节机制。这意味着每次使用可以做出有趣的选择。这里是一些需要思考的问题。 + +### What operations should be provided? + +### 应该提供什么的操作? + +This is the biggest question. It deeply affects how this pattern feels and how +well it works. At the minimal end of the spectrum, the base class doesn't +provide *any* operations. It just has a sandbox method. To implement it, you'll +have to call into systems outside of the base class. If you take that angle, +it's probably not even fair to say you're using this pattern. + +这是最大的问题。这深深影响了这个模式感觉上证明样和它能工作的有多好。,在一种极端中,基类几乎不提供*任何*操作。他只有一个沙箱方法。为了实现之,你需要调用基类外部的系统。如果你从那个角度看,很难说你在使用这个模式。 + +On the other end of the spectrum, the base class provides *every* operation that a subclass may need. Subclasses are +*only* coupled to the base class and don't call into any outside systems +whatsoever. + +另一个极端,基类提供了*所有*子类也许需要的操作。子类*只*与基类耦合,不调用任何外部系统的东西。 + + + +Between these two points, there's a wide middle ground where some operations are +provided by the base class and others are accessed directly from the outside +system that defines it. The more operations you provide, the less coupled +subclasses are to outside systems, but the *more* coupled the base class is. It +removes coupling from the derived classes, but it does so by pushing that up to the +base class itself. + +在这两点之间,操作由基类提供还是直接调用外部有很大的操作余地。你提供的操作越多,子类与外部系统耦合越少,但是与基类耦合*更多*。它从推导类中移除了耦合,但它是通过将其推给基类完成的。 + +That's a win if you have a bunch of derived classes that were all coupled to +some outside system. By moving the coupling up into a provided operation, you've +centralized it into one place: the base class. But the more you do this, the +bigger and harder to maintain that one class becomes. + +如果你有一堆推导类与外部系统耦合的话,这是一个胜利。通过将耦合移到一个提供的操作,你将其移动到了一个地方:基类。但是你越这么做,那个类就越大越难管理。 + +So where should you draw the line? Here are a few rules of thumb: + +所以分界线在哪里?这里是一些首要原则: + + * If a provided operation is only used by one or a few subclasses, you don't + get a lot of bang for your buck. You're adding complexity to the base class, + which affects everyone, but only a couple of classes benefit. + + * 如果一个提供的操作只被一个或几个子类使用,你就获益不了太多。你向基类添加了复杂性,会影响所有事物,但是只有几个类收益。 + + This may be worth it to make the operation consistent with other + provided operations, or it may be simpler and cleaner to let those + special case subclasses call out to the external systems directly. + + 也许让操作与其他提供的操作一致更有价值,或者让这些特殊情况子类直接调用外部系统更加简单明了。 + + * When you call a method in some other corner of the game, it's less intrusive + if that method doesn't modify any state. It still creates a coupling, but + it's a "safe" coupling because it can't break + anything in the game. + + * 当你调用游戏中其他交流的一个方法,如果方法没有修改任何状态就有更少的干扰。它仍然制造耦合,但是这是“安全”耦合,因为它没有打破游戏中的任何东西。 + + + + Calls that do modify state, on the other hand, more deeply tie you to those + parts of the codebase, and you need to be much more cognizant of that. That + makes them good candidates for being rolled up into provided operations in + the more visible base class. + + 调用修改状态的,另一方面,将你和代码库的其他方面紧密绑定,你需要更多的审视。打包他们进一个显式基类提供的操作是一个好的候选项。 + + * If the implementation of a provided operation only forwards a call to some + outside system, then it isn't adding much value. In that case, it may be + simpler to call the outside method directly. + + * 如果提供操作的实现之是增加了向外部系统转发调用,那它就没增加什么价值。那种情况下,也许直接调用外部方法更简单。 + + However, even simple forwarding can still be useful -- those methods often + access state that the base class doesn't want to directly expose to + subclasses. For example, let's say `Superpower` provided this: + + 但是,哪怕简单的转发也是有用的——那些接触基类不想直接暴露被子类的状态的方法。举个例子,假设`Superpower`提供这个: + + ^code 5 + + It's just forwarding the call to some `soundEngine_` field in `Superpower`. + The advantage, though, is that it keeps that field encapsulated in + `Superpower` so subclasses can't poke at it. + + 它只是转发`Superpower`中`soundEngine_`字段。但是,好处是褒词字段封装在`Superpower`中,子类不会接触它。 + +### Should methods be provided directly, or through objects that contain them? + +### 方法应该直接提供,还是通过包含他们的对象? + +The challenge with this pattern is that you can end up with a painfully large +number of methods crammed into your base class. You can mitigate that by moving +some of those methods over to other classes. The provided operations in the base +class then just return one of those objects. + +这个模式的挑战是基类中最终闯进了很多方法。你可以将一些方法移到其他类中来缓和。基类提供的方法只是放回这些对象中的一个。 + +For example, to let a power play sounds, we could add these directly to +`Superpower`: + +举个例子,为了让一个超能力播放声音,我们可以直接将它们加到`Superpower`中: + +^code 6 + +But if `Superpower` is already getting large and unwieldy, we might want to +avoid that. Instead, we create a `SoundPlayer` class that exposes that +functionality: + +但是如果`Superpower`已经很大很宽泛,我们也许想要避免那一点。相反,我们创建一个`SoundPlayer`类暴露那个函数: + +^code 7 + +Then `Superpower` provides access to it: + +`Superpower`提供了对其的接触: + +^code 8 + +Shunting provided operations into auxiliary classes like this can do a few +things for you: + +将提供的操作分到辅助类可以为你做一些事情: + + * *It reduces the number of methods in the base class.* In the example here, + we went from three methods to just a single getter. + + * *它减少了基类的方法。*在这里的例子,将三个方法变成了一个简单的获取。 + + * *Code in the helper class is usually easier to maintain.* Core base classes + like `Superpower`, despite our best intentions, tend to be tricky to change + since so much depends on them. By moving functionality over to a less + coupled secondary class, we make that code easier to poke at without + breaking things. + + * *在辅助类中的代码通常更好管理。*核心基类像`Superpower`,不管我们滴最好意图,由于被如此多类依赖而很难改变。通过将函数移到一个更少耦合的二阶类,我们让代码更容易接触而没有破坏任何东西。 + + * *It lowers the coupling between the base class and other systems.* When + `playSound()` was a method directly on `Superpower`, our base + class was directly tied to `SoundId` and whatever audio code the + implementation called into. Moving that over to `SoundPlayer` reduces + `Superpower`'s coupling to the single `SoundPlayer` class, which then + encapsulates all of its other dependencies. + + * *减少了基类和其他系统的耦合度。*当`playSound()`是`Superpower`中一个直接的方法,我们的基类与`SoundId`以及其他实现涉及的代码直接绑定。将它移动到`SoundPlayer`减少了`Superpower`与`SoundPlayer`类的耦合,这就封装了它其他的依赖。 + +### How does the base class get the state that it needs? + +### 基类如何获得它需要的状态? + +Your base class will often need some data that it wants to encapsulate and keep +hidden from its subclasses. In our first example, the `Superpower` class +provided a `spawnParticles()` method. If the implementation of that needs some +particle system object, how would it get one? + +你的基类经常需要想要封装和从子类隐藏的数据。在我们的第一个例子中,`Superpower`类提过了`spawnParticles()`方法。乳沟那个的实现需要一些粒子系统对象,它怎么获得一个呢? + + * **Pass it to the base class constructor:** + + * **将它传给基类构造器:** + + The simplest solution is to have the base class take it as a constructor + argument: + + 最简单的解决方案是让基类将其作为构造器变量: + + ^code pass-to-ctor-base + + This safely ensures that every superpower does have a particle system by the + time it's constructed. But let's look at a derived class: + + 这安全的保证了每个超能力在构造时有一个粒子系统。但是让我们看看推导类: + + ^code pass-to-ctor-sub + + Here we see the problem. Every derived class will need to have a constructor + that calls the base class one and passes along that argument. That exposes + every derived class to a piece of state that we don't want them to know + about. + + 这我们看到了问题。每一个推导类都需要一个构造器调用基类传递那个变量。这项推导类暴露了我们不想要他知道的状态。 + + This is also a maintenance headache. If we later add another piece of state + to the base class, every constructor in each of our derived classes will + have to be modified to pass it along. + + 这也让维护头疼。如果我们后续向基类添加了一个状态,每一个推导类都需要修改并传递它。 + + * **Do two-stage initialization:** + + * **使用两阶初始化:** + + To avoid passing everything through the constructor, we can split + initialization into two steps. The constructor will take no parameters and + just create the object. Then, we call a separate method defined directly on + the base class to pass in the rest of the data that it needs: + + 为了避免通过构造器传递所有东西,我们可以将初始化划分为两个部分。构造器不接受任何参数只是创建对象。然后,我们调用一个定义在基类的分离方法传入所有他需要的数据。 + + ^code 9 + + Note here that since we aren't passing anything into the constructor for + `SkyLaunch`, it isn't coupled to anything we want to keep private in + `Superpower`. The trouble with this approach, though, is that you have to + make sure you always remember to call `init()`. If you ever forget, you'll + have a power that's in some twilight half-created state and won't work. + + 注意我们没有为`SkyLaunch`的构造器传入任何东西,它与`Superpower`中想要保持 私有的任何东西都不耦合。这种实现的问题,是你需要保证你用于记得调用`init()`,你会获得一个处于半完成的超能力,无法运行。 + + You can fix that by encapsulating the entire process into a single function, + like so: + + 你可以将整个过程封装到一个函数中来修复这一点,就像这样: + + + + ^code 10 + + + + * **Make the state static:** + + * **让状态变为静态:** + + In the previous example, we were initializing each `Superpower` *instance* + with a particle system. That makes sense when every power needs its own + unique state. But let's say that the particle system is a Singleton, and every power will be sharing the + same state. + + 在前一个例子中,我们用粒子系统初始化每一个`Superpower`*实例*。这在每个能力都需要它独特的状态时有意义。但是假设粒子系统是一个单例,每一个能力都会分享相同的状态。 + + In that case, we can make the state private to the base class and also make + it *static*. The game will still have to make + sure that it initializes the state, but it only has to initialize the + `Superpower` *class* once for the entire game, and not each instance. + + 在那种情况下,我们可以让状态时基类私有而*静态*的。游戏仍然要保证初始化转台,但是它只需要为整个游戏初始化`Superpower`*类*一次,而不是为每一个实例。 + + + + ^code 11 + + Note here that `init()` and `particles_` are both static. As long as the + game calls `Superpower::init()` once early on, every power can access the + particle system. At the same time, `Superpower` instances can be created + freely by calling the right derived class's constructor. + + 注意这里的`init()`和`particles_`都是静态的。主要游戏早先调用过一次`Superpower::init()`,每种能力都能接触粒子系统。同时,`Superpower`实例可以通过调用推导类的构造器自由创建。 + + Even better, now that `particles_` is a *static* variable, we don't have to + store it for each instance of `Superpower`, so we've made the class use less + memory. + + 甚至更好的,现在`particles_`是一个*静态*变量,我们不需要在每一个`Superpower`中存储它,这样我们的类需要更少的内存。 + + * **Use a service locator:** + + * **使用服务定位器:** + + The previous option requires that outside code specifically remembers to push + in the state that the base class needs before it needs it. That places the + burden of initialization on the surrounding code. Another option is to let + the base class handle it by pulling in the state it needs. One way to do + that is by using the Service + Locator pattern: + + 前一个选项需要外部代码记得在基类需要前设置状态。这将初始化的责任落在了周围的代码。另一个选项是让基类通过调出它需要的状态来实现。一种实现的方法是使用一个服务定位器模式: + + ^code 12 + + Here, `spawnParticles()` needs a particle system. Instead of being *given* + one by outside code, it fetches one itself from the service locator. + + 这儿,`spawnParticles()`需要一个粒子系统,不是外部系统*给*它,它自己从服务定位器中拿了一个。 + +## See Also + +## 参见 + + * When you apply the Update + Method pattern, your update method will often also be a sandbox method. + + * 但你使用更新模式时,你的更新模式通常也是沙箱方法。 + + * This pattern is a role reversal of the Template + Method pattern. In both patterns, you implement a method using a set of + primitive operations. With Subclass Sandbox, the method is in the derived + class and the primitive operations are in the base class. With Template + Method, the *base* class has the method and the primitive operations are + implemented by the *derived* class. + + * 这个模式时模板方法的反面角色。在两种模式中,你都使用一系列受限操作实现方法。子啊子类沙箱总,方法在推导类中,限制操作在基类中。在模板方法中,*基*类有方法,而受限操作被*推导*类实现。 + + * You can also consider this a variation on the Facade pattern. That + pattern hides a number of different systems behind a single simplified API. + With Subclass Sandbox, the base class acts as a facade thats hides the + entire game engine from the subclasses. + + * 你也可以考虑这是外观模式的变异。那个模式将一系列不同系统藏在一个简化的API后。使用子类沙箱,基类起到了在子类前隐藏整个游戏引擎的外观作用。 diff --git a/book/type-object.markdown b/book/type-object.markdown new file mode 100644 index 0000000..7948c6b --- /dev/null +++ b/book/type-object.markdown @@ -0,0 +1,934 @@ +^title Type Object +^section Behavioral Patterns + +## Intent + +## 意图 + +*Allow the flexible creation of new "classes" by creating a single class, each +instance of which represents a different type of object.* + +*创造一个类来允许灵活的创造新“类”,类的每个实例都代表了不同类型的对象。* + +## Motivation + +## 动机 + +Imagine we're working on a fantasy role-playing game. Our task is to write the +code for the hordes of vicious monsters that seek to slay our brave hero. +Monsters have a bunch of different attributes: health, attacks, graphics, +sounds, etc., but for example purposes we'll just worry about the first two. + +想象我们在制作一个超棒的角色扮演游戏。我们的任务是为一群想要杀死我们英雄的恶毒怪物编写代码。怪物有多个的属性:健康,攻击,图形,声音,等等。但是为了演示目的我们先只考虑前面两个。 + +Each monster in the game has a value for its current health. It starts out full, +and each time the monster is wounded, it diminishes. Monsters also have an +attack string. When the monster attacks our hero, that text will be shown to the +user somehow. (We don't care how here.) + +游戏中的每一个怪物都有一个当前健康的值。它开始时是满的,每一次怪物受伤,它就下降。关务也有一个攻击字符。当怪物攻击我们的英雄,那个文本就会以某种方式展示给用户。(我们不在乎这里怎样实现。) + +The designers tell us that monsters come in a variety of different *breeds*, +like "dragon" or "troll". Each breed describes a *kind* of monster that exists +in the game, and there can be multiple monsters of the same breed running around +in the dungeon at the same time. + +设计者告诉我们怪物有不同*品种*,像“龙”或者“巨魔”。每一个品种都描述了一*种*存在于游戏中的怪物,同时可能有多个同种怪物在地牢里游荡。 + +The breed determines a monster's starting health -- dragons start off with more +than trolls, making them harder to kill. It also determines the attack string -- +all monsters of the same breed attack the same way. + +品种决定了怪物的初始健康——龙开始比巨魔多,让他们更难被杀死。这也决定了攻击字符——同种的所有怪物都以相同方式攻击。 + +### The typical OOP answer + +### 传统的面向对象方案 + +With that game design in mind, we fire up our text editor and start coding. According +to the design, a dragon is a kind of monster, a troll is +another kind, and so on with the other breeds. Thinking object-oriented, that +leads us to a `Monster` base class: + +脑中有这样的设计方案,我们启动了我们的文本编辑器开始编程。根据设计,龙是一种怪物,巨魔是另一种,其他种类的也是一样。考虑面向对象,这引导我们创建一个`Monster`基类。 + + + +^code 1 + +The public `getAttack()` function lets the combat code get the string that +should be displayed when the monster attacks the hero. Each derived breed class +will override this to provide a different message. + +公开的`getAttack()`函数让战斗代码获得在怪物攻击英雄时需要显示的文字。每一个推导类都需要重载它来提供一个不同的消息。 + +The constructor is protected and takes the starting health for the monster. +We'll have derived classes for each breed that provide their own public +constructors that call this one, passing in the starting health that is +appropriate for that breed. + +构造器是被保护的,需要传入怪物的初始健康。我们为每个品种的推导类提供公共构造器调用这一个,传入对于该品种适合的起始血量。 + +Now let's see a couple of breed subclasses: + +现在让我们看看一些品种子类: + + + +^code 2 + + + +Each class derived from `Monster` passes in the starting health and overrides +`getAttack()` to return the attack string for that breed. Everything works as +expected, and before long, we've got our hero running around slaying a variety of +beasties. We keep slinging code, and before we know it, we've got dozens of +monster subclasses, from acidic slimes to zombie goats. + +每一个个从`Monster`推导出来的类都传入起始血量并重载`getAttack()`来返回那个品种的攻击字符串。所有事情都一如所料的工作,不久以后,我们的英雄就可以跑来跑去杀死各种野兽了。我们继续编程,在我们意识到之前,我们就有了成打的怪物子类,从酸泥怪到僵尸羊。 + +Then, strangely, things start to bog down. Our designers ultimately want to have +*hundreds* of breeds, and we find ourselves spending all of our time writing +these little seven-line subclasses and recompiling. It gets worse -- the designers +want to start tuning the breeds we've already coded. Our formerly productive +workday degenerates to: + +然后,奇怪的是,事情陷入困境。我们的设计者最终想要*几百个*品种,但是我们发现我们花费了所有的时间在写这些启航子类和重新编译上。这会继续变糟——设计者想要协调我们已经编码的品种。我们之前的工作日退化成了: + +1. Get email from designer asking to change health of troll from + 48 to 52. + +1. 收到设计者将巨魔的血量从48改到52的邮件。 + +2. Check out and change `Troll.h`. + +2. 签出并修改`Troll.h`。 + +3. Recompile game. + +3. 重新编译游戏。 + +4. Check in change. + +4. 签入修改。 + +5. Reply to email. + +5. 回复邮件。 + +6. Repeat. + +6. 重复。 + +We spend the day frustrated because we've turned into data monkeys. Our +designers are frustrated because it takes them forever to get a simple +number tuned. What we need is the ability to change breed stats without having +to recompile the whole game every time. Even better, we'd like designers to be +able to create and tune breeds without *any* programmer intervention at all. + +我们度过了失意的一天,因为我们变成了数据猴子。我们的设计者感到挫败因为修改一个数据就要老久。我们需要的是无需每次重新编译游戏就能修改品种的状态。甚至更好的,我们想要设计者恩狗创建和修改品类无需*任何*程序员的介入。 + +### A class for a class + +### 为类建类 + +At a very high level, the problem we're trying to solve is pretty simple. We +have a bunch of different monsters in the game, and we want to share certain +attributes between them. A horde of monsters are beating on the hero, and we +want some of them to use the same text for their attack. We define that by +saying that all of those monsters are the same "breed", and that the breed +determines the attack string. + +在一个非常高的层次,我们试图解决的问题非常简单。游戏中有很多不同的怪物,我们想要在他们之间分享属性。一大群怪物子啊攻击英雄,我们想要他们其中的一些使用相同的攻击文本。我们通过声明这些怪物是相同“品种”,而那个品种决定了攻击字符串。 + +We decided to implement this concept using inheritance since it lines up with +our intuition of classes. A dragon is a monster, and each dragon in the game is +an instance of this dragon "class". Defining each breed as a subclass of an +abstract base `Monster` class, and having each monster in the game be an +instance of that derived breed class mirrors that. We end up with a class +hierarchy like this: + +由于它与我们和类的直觉相关,我们决定实现这个模式。一条龙是一个怪物,每一条龙都是龙“类”的实例。定义每个品种是抽象基类`Monster` 的子类,让每个游戏中的怪物都是推导类的实例反映了那一点。我们最终由一个这样的类层次: + + + +A Monster base class with derived classes for Dragon, Troll, etc. + + + +Each instance of a monster in the game will be of one of the derived monster +types. The more breeds we have, the bigger the class hierarchy. That's the +problem of course: adding new breeds means adding new code, and each breed has +to be compiled in as its own type. + +每一个怪物的实例都是一个推导怪物类型。我们有的品种越多,类层次越大。这当然是问题:添加新品种就是添加新代码,而每一个品种都需要被编译为它自己的类型。 + +This works, but it isn't the only option. We could also architect our code so +that each monster *has* a breed. Instead of subclassing `Monster` for each +breed, we have a single `Monster` class and a single `Breed` class: + +这可行,但不是唯一的选项。我们也可以架构代码为每一个怪物*有*一个品种。不是让每个品种继承`Monster`,我们现在有单一的`Monster`类和`Breed`类。 + + + +A Monster object has a reference to a Breed object. + + + +That's it. Two classes. Notice that there's no inheritance at all. With this +system, each monster in the game is simply an instance of class `Monster`. The +`Breed` class contains the information that's shared between all monsters of the +same breed: starting health and the attack string. + +这就成了。两个类。注意这里完全没有继承。通过这个系统,游戏中的每一个怪物都是`Monster`的一个实例。`Breed`类包含了在不同品种怪物间分享的信息:开始健康和攻击字符串。 + +To associate monsters with breeds, we give each `Monster` instance a reference +to a `Breed` object containing the information for that breed. To get the attack +string, a monster just calls a method on its breed. The `Breed` class +essentially defines a monster's "type". Each breed instance is an *object* that +represents a different conceptual *type*, hence the name of the pattern: Type +Object. + +为了将怪物与品种相关联,我们给了每一个`Monster`实例一个对包含品种信息的`Breed`对象的引用。为了获得攻击字符串,一个关守可以在对她的子类调用方法。`Breed`类本质上定义了一个怪物的*类型*,因此模式的名字是类型对象。 + +What's especially powerful about this pattern is that now we can define new +*types* of things without complicating the codebase at all. We've essentially +lifted a portion of the type system out of the hard-coded class hierarchy into +data we can define at runtime. + +这个模式特别有用的一点是我们现在可以定义全新的*类型*而无需搅乱代码库。我们本质上讲一部分类型系统从硬编码的继承结构中拉出放到我们可以在运行时定义的数据中去。 + +We can create hundreds of different breeds by instantiating more instances +of `Monster` with different values. If we create breeds by initializing them from +data read from some configuration file, we have the ability to define new types +of monsters completely in data. So easy, a designer could do it! + +我们可以通过通过用不同值实例化`Monster`来创建成百上千的新品种。如果通过从一些配置文件读取不同的数据来初始化品种,我们就有能力完全靠数据定义新怪物品种。这么容易,一个设计者也可以做到! + +## The Pattern + +## 模式 + +Define a **type object** class and a **typed object** class. Each type object +instance represents a different logical type. Each typed object stores a +**reference to the type object that describes its type**. + +定义一个**类型对象**类和一个**有类型的对象**类。每一种类型对象实例代表一种不同的逻辑类型,每一种有类型的对象保存一个**描述它的类型的对类型对象的引用**。 + +Instance-specific data is stored in the typed object instance, and data or +behavior that should be shared across all instances of the same conceptual type +is stored in the type object. Objects referencing the same type object will +function as if they were the same type. This lets us share data and behavior +across a set of similar objects, much like subclassing lets us do, but without +having a fixed set of hard-coded subclasses. + +实例相关的数据被存储在有类型对象实例中,应该被同种类型分享的数据或者行为存储子啊类型对象中。以用户相同类型对象的对象将会向他们是同一类型一样运作。这让我们在一组相托的对象间分享行为和数据,就像子类让我们做的那样,到那时没有固定的硬编码子类集合。 + +## When to Use It + +## 何时使用 + +This pattern is useful anytime you need to define a variety of different "kinds" +of things, but baking the kinds into your language's type system is too rigid. +In particular, it's useful when either of these is true: + +这个模式在任何你需要定义不同“种”,但是直接运用你语言的类型系统过于僵硬的时候。特别的,下面两者之一都是有用的: + + * You don't know what types you will need up front. (For example, what if our + game needed to support downloading content that contained new breeds of + monsters?) + + * 你不知道你后面还需要什么类型。(举个例子,如果你的游戏需要支持下载后续新包含进来的怪物品种呢?) + + * You want to be able to modify or add new types without having to recompile + or change code. + + * 你想要能不编译或改变代码就能修改或添加新类型。 + +## Keep in Mind + +## 记住 + +This pattern is about moving the definition of a "type" from the imperative but +rigid language of code into the more flexible but less behavioral world of +objects in memory. The flexibility is good, but you lose some things by hoisting +your types into data. + +这个模型关于将“类型”的定义从必要但是僵硬的语言世界移到灵活但是更少行为的对象行为世界。灵活性很好,但是将类型提高到数据丧失了一些东西。 + +### The type objects have to be tracked manually + +### 类型对象需要手动追踪 + +One advantage of using something like C++'s type +system is that the compiler handles all of the bookkeeping for the classes +automatically. The data that defines each class is automatically compiled into +the static memory segment of the executable and just works. + +使用C++类型系统的好处之一就是编译器控制了自动记录类的工作。定义每个类的数据自动编译到可执行的静态内存段然后工作。 + +With the Type Object pattern, we are now responsible for managing not only our +monsters in memory, but also their *types* -- we have to make sure all of the +breed objects are instantiated and kept in memory as long as our monsters need +them. Whenever we create a new monster, it's up to us to ensure that it's +correctly initialized with a reference to a valid breed. + +使用类型对象模式,我们现在不但要负责管理内存中的怪物,同时要管理他们的*类型*——我们要保证所有的品种对象都被实例化并保存子啊内存中只要我的怪物需要它。无论何时我们创建了一个新的怪物,这由我们来保证它别初始化为有一个对合理种类的引用。 + +We've freed ourselves from some of the limitations of the compiler, but the cost +is that we have to re-implement some of what it used to be doing for us. + +我们解放了一些编译器的限制,但是代价是我们需要重新实现一些它以前为我们做的事情。 + + + +### It's harder to define *behavior* for each type + +### 更难为每种对象定义*行为* + +With subclassing, you can override a method and do whatever you want to -- +calculate values procedurally, call other code, etc. The sky is the limit. We +could define a monster subclass whose attack string changed based on the phase +of the moon if we wanted to. (Handy for werewolves, I suppose.) + +通过子类,你可以重载方法然后做你想做的事——用程序计算值,调用其他代码,等等。天就是限制。如果我们想的话,我们可以定义一个怪物子类,根据月亮的阶段改变它的攻击字符串。(就像是狼人,我觉得。) + +When we use the Type Object pattern instead, we replace an overridden method +with a member variable. Instead of having monster subclasses that override a +method to *calculate* an attack string using different *code*, we have a breed +object that *stores* an attack string in a different *variable*. + +当我们使用类型对象模式的时候,我们将重载方法替换成了一个成员变量。不是让怪物的子类重载一个方法用不同的*代码*来*计算*一个攻击字符串,我们有一个品种对象在不同的*变量*中*存储*攻击字符串。 + +This makes it very easy to use type objects to define type-specific *data*, but +hard to define type-specific *behavior*. If, for example, different breeds of +monster needed to use different AI algorithms, using this pattern becomes more +challenging. + +这让使用类型对象定义类相关的*数据*变得容易,但是定义类型相关的*行为*困难。如果,举个例子,不同的品种的怪物需要使用不同的AI算法,使用这个模式就更加成为一个挑战。 + +There are a couple of ways we can get around this limitation. A simple solution +is to have a fixed set of pre-defined behaviors and then use data in the type +object to simply *select* one of them. For example, let's say our monster AI +will always be either "stand still", "chase hero", or "whimper and cower in +fear" (hey, they can't all be mighty dragons). We can define functions to implement each of those behaviors. Then, we can +associate an AI algorithm with a breed by having it store a pointer to +the appropriate function. + +这有很多方式我们可以跨越这个限制。一个简单的方式是使用一个固定的预先定义的行为然后使用类型对象中的数据简单的*选择*他们中的一个。举例,假设我们的怪物AI总是要么“站着不动”“追逐英雄”或者“恐惧地呜咽颤抖”(嘿,他们不可能都是强势的龙)。我们可以定义函数来实现每一种行为。然后,我们通过存储指向合适函数的方法将AI算法与品种相关联。 + + + +Another more powerful solution is to actually support defining behavior +completely in data. The Interpreter and Bytecode patterns both let us build +objects that represent behavior. If we read in a data file and use that to +create a data structure for one of these patterns, we've moved the behavior's +definition completely out of code and into content. + +另一个更加有力的解决方案是真正的子啊数据中支持定义行为。翻译器模式和字节码模式让我们定义有行为的对象。如果我们阅读数据文件然后用它为两者之一构建数据结构,我们就将行为完全从代码中移出放入了内容。 + + + +## Sample Code + +## 示例代码 + +For our first pass at an implementation, let's start simple and build the basic +system described in the motivation section. We'll start with the `Breed` +class: + +在我们的第一次实现过程中,让我们从简单的开始,只建构动机那节提到的基础系统。我们从`Breed`类开始: + +^code 3 + +Very simple. It's basically just a container for two data fields: the starting +health and the attack string. Let's see how monsters use it: + +很简单。它基本上只是两个数据字段的容器:起始血量和攻击字符串。让我们看看怪物怎么使用它: + +^code 4 + +When we construct a monster, we give it a reference to a breed object. This +defines the monster's breed instead of the subclasses we were previously using. +In the constructor, `Monster` uses the breed to determine its starting health. +To get the attack string, the monster simply forwards the call to its breed. + +当我们建构怪物,我给它一个种类对象的引用。这定义了了关务的种类而不是用我们之前的子类。在构造器中,`Monster`使用种类决定它的起始血量。为了得到攻击字符串,怪物简单的将调用转发给它的种类。 + +This very simple chunk of code is the core idea of the pattern. Everything from +here on out is bonus. + +这种非常简单的代码是这章的核心思路。由此得出的任何东西都是红利。 + +### Making type objects more like types: constructors + +### 让类型对象更像类型:构造器 + +With what we have now, we construct a monster directly and are responsible for +passing in its breed. This is a bit backwards from how regular objects are +instantiated in most OOP languages -- we don't usually allocate a blank chunk of +memory and then *give* it its class. Instead, we call a constructor function on +the class itself, and it's responsible for giving us a new instance. + +使用我们现有的,我们可以直接构造一个怪物然后负责传入它的种类。这比常用的OOP语言实现的对象有些退步——我们通常不分配一块空白内存然后*给*它它的类。相反,我们根据类调用构造器,它负责给我们一个新实例。 + +We can apply this same pattern to our type objects: + +我们可以在我们的类型对象上应用同样的模式。 + +^code 5 + + + +And the class that uses them: + +以及那个使用他们的类: + +^code 6 + +The key difference is the `newMonster()` function in +`Breed`. That's our "constructor" factory method. With our original +implementation, creating a monster looked like: + +核心不同是`Breed`中的`newMonster()`。这是我们的“构造器”工厂方法。使用我们原先的是吸纳,创建一个怪物看上去像: + + + +^code 7 + +After our changes, it's like this: + +在我们改动后,它看上去是: + +^code 8 + +So, why do this? There are two steps to creating an object: allocation and +initialization. `Monster`’s constructor lets us do all of the +initialization we need. In our example, that's only storing the breed, but a +full game would be loading graphics, initializing the monster's AI, and doing +other set-up work. + +所以,为什么这么做?这里有两步来创作一个对象:分配和初始化。`Monster`的构造器让我们做完了所有我们需要的初始化。在我们的例子中,暗示存储类型的唯一地方,但是一个完整的游戏需要加载图形,初始化怪物AI以及做其他的设置工作。 + +However, that all happens *after* allocation. We've already got a chunk of +memory to put our monster into before its constructor is called. In games, we +often want to control that aspect of object creation too: we'll typically use +things like custom allocators or the Object Pool pattern to control where in memory our +objects end up. + +但是,那都发生在分配*之后*。我们已经在构造器调用前找到了一块内存放置我们的怪物。在游戏中,我们通常也想控制对象创造的层面:我们通常使用自定义的分配器或者对象池模式来控制我们的对象最终在内存的何处。 + +Defining a "constructor" function in `Breed` gives us a place to put that logic. +Instead of simply calling `new`, the `newMonster()` function can pull the memory +from a pool or custom heap before passing control off to `Monster` for +initialization. By putting this logic inside `Breed`, in the *only* function +that has the ability to create monsters, we ensure that all monsters go through +the memory management scheme we want. + +在`Breed`中定义一个“构造器”函数给了我们地方放置这些逻辑。不是简单的调用`new`,`newMonster()`函数可以在将控制传递给`Monster`初始化之前从池中或堆中获取内存。通过放置这些逻辑在`Breed`内部*唯一*有能力创建怪物的函数中,我们保证了所有的怪物变量遵守了内存管理规范。 + +### Sharing data through inheritance + +### 通过继承分享数据 + +What we have so far is a perfectly serviceable type object system, but it's +pretty basic. Our game will eventually have *hundreds* of different breeds, each +with dozens of attributes. If a designer wants to tune all of the thirty +different breeds of troll to make them a little stronger, she's got a lot of +tedious data entry ahead of her. + +我们现在有的是一个完美服务性的类型对象系统,但是它非常简单。我们的游戏最终由*上百*中不同种类,每种都有成打的特性。如果一个设计者想要协调30中不同的巨魔让它们变得强壮一点,她就有冗长的数据入口摆在它面前。 + +What would help is the ability to share attributes across multiple *breeds* in +the same way that breeds let us share attributes across multiple *monsters*. +Just like we did with our original OOP solution, we can solve this using +inheritance. Only, this time, instead of using our language's inheritance +mechanism, we'll implement it ourselves within our type objects. + +能帮上忙的是在不同的*种类*间分享属性的能力,一如种类让我们在不同的*怪物*间分享属性的能力。就像我们对之前OOP方案做的那样,我们可以使用继承完成这一点。只是,这次,不使用语言的继承机制,我们用类型对象实现它。 + +To keep things simple, we'll only support single inheritance. In the same way +that a class can have a parent base class, we'll allow a breed to have a parent +breed: + +为了让事保持简单,我们只支持单继承。某种程度上一个类可以有一个父基类,我们允许品种有一个父品种。 + +^code 9 + +When we construct a breed, we give it a parent that it inherits from. We can +pass in `NULL` for a base breed that has no ancestors. + +当我们构建一个品种,我们把它交给他继承的父母。我们可以为基础种类传入一个`NULL`表明它没有祖先。 + +To make this useful, a child breed needs to control which attributes are +inherited from its parent and which attributes it overrides and specifies itself. For our +example system, we'll say that a breed overrides the monster's health by having +a non-zero value and overrides the attack by having a non-`NULL` string. +Otherwise, the attribute will be inherited from its parent. + +为了让这个有用,一个子品种需要控制它从父品种继承了什么东西,以及哪些属性它重载并由自己指定。对我们的例子系统,我们可以说一个种类用非零值重载了怪物的健康,通过非空字符串重载了攻击字符串。否则,这些属性要从它的父母继承。 + +There are two ways we can implement this. One is to handle the delegation +dynamically every time the attribute is requested, like this: + +有两种方式我们可以实现之。一种是每次属性被请求是东塔处理委托,就像这样: + +^code 10 + +This has the advantage of doing the right thing if a breed is modified at +runtime to no longer override, or no longer inherit some attribute. On the other +hand, it takes a bit more memory (it has to retain a pointer to its parent), and +it's slower. It has to walk the inheritance chain each time you look up an +attribute. + +当种类在运行时修改他不为重载,或者不再继承某些属性时,这个有做正确的事的优势。另一方面,他需要更多的内存(他需要保存一个指针指向它的父母)而且更慢。每一次你寻找一个属性都需要回溯继承链。 + +If we can rely on a breed's attributes not changing, a faster solution is to +apply the inheritance at *construction time*. This is called "copy-down" +delegation because we *copy* inherited attributes *down* into the derived type +when it's created. It looks like this: + +如果我们可以依赖一个品种的属性不改变,一个更快的解决方案是在*构造时*引用继承。这被称为“复制下来”委托,因为当创建对象时,我们*复制*继承的属性*吓到*推导的类型。它看上去是这样的: + +^code copy-down + +Note that we no longer need a field for the parent breed. Once the constructor +is done, we can forget the parent since we've already copied all of its +attributes in. To access a breed's attribute, now we just return the field: + +现在我们不再需要一个字段给父种类了。一旦构造器完成,我们可以忘了父品种,因为我们已经拷贝了它的所有属性。为了获得一个种类的属性,我们现在直接返回字段: + +^code copy-down-access + +Nice and fast! + +又好又快! + +Let's say our game engine is set up to create the breeds by loading a JSON file +that defines them. It could look like: + +假设我们的游戏引擎设置为加载一个定义品种的JSON文件后创建类型。他看上去是这样的: + + :::json + { + "Troll": { + "health": 25, + "attack": "The troll hits you!" + }, + "Troll Archer": { + "parent": "Troll", + "health": 0, + "attack": "The troll archer fires an arrow!" + }, + "Troll Wizard": { + "parent": "Troll", + "health": 0, + "attack": "The troll wizard casts a spell on you!" + } + } + + +We'd have a chunk of code that reads each breed entry and instantiates a new +breed instance with its data. As you can see from the `"parent": "Troll"` +fields, the `Troll Archer` and `Troll Wizard` breeds inherit from the base +`Troll` breed. + +我们有一堆代码读取每个品种的入口,用新的数据实例化一个品种实例。就像你从`"parent": "Troll"`字段看到的, `Troll Archer`和`Troll Wizard`种类都由基础`Troll`种类继承而来。 + +Since both of them have zero for their health, they'll inherit it from the base +`Troll` breed instead. This means now our designer can tune the health in +`Troll` and all three breeds will be updated. As the number of breeds and the +number of different attributes each breed has increase, this can be a big +time-saver. Now, with a pretty small chunk of code, we have an open-ended system +that puts control in our designers' hands and makes the best use of their time. +Meanwhile, we can get back to coding other features. + +由于他们的初始血量都是0,他们会从基础`Troll`种类继承。这意味着无论我们怎么协调`Troll`的学历三个种类都会被更新。随着种类的数量和属性的数量增加,这节约了很多时间。现在,哦听过一小块嗲吗,我们有一个开放式的系统给了我们设计者控制权,并让他们好好利用时间。同时,我们可以回去编码其他特性了。 + +## Design Decisions + +## 设计决策 + +The Type Object pattern lets us build a type system as if we were designing our +own programming language. The design space is wide open, and we can do all sorts +of interesting stuff. + +类型对象模式让我们建立一个类型系统就好像我们在设计我们自己的编程语言。设计空间是开发的,我们可以做很多有趣的事情。 + +In practice, a few things curtail our fancy. Time and maintainability will +discourage us from anything particularly complicated. More importantly, whatever +type object system we design, our users (often non-programmers) will need to be +able to easily understand it. The simpler we can make it, the more usable it +will be. So what we'll cover here is the well-trodden design space, and we'll +leave the far reaches for the academics and explorers. + +在实践中,有些东西削减了我们的幻想。时间和可维护性阻止我们创建特别复杂的东西。更重要的是,无论我们设计了怎样的类型系统,我们的用户(通常不是程序员)要能轻松理解它。我们将其做的越简单,它就越有用。所以我们在这里覆盖的是陈旧的设计空间,我们会将其留给艺术家和探索者。 + +### Is the type object encapsulated or exposed? + +### 类型对象是封装的还是暴露的? + +In our sample implementation, `Monster` has a reference to a breed, but it +doesn't publicly expose it. Outside code can't get directly at the monster's +breed. From the codebase's perspective, monsters are essentially typeless, and +the fact that they have breeds is an implementation detail. + +在我们的简单实现中,`Monster`有一个对种类的引用,但是它没有显式暴露它。外部代码不能直接获取怪物的品种。从代码库的角度看来,怪物基本上没有类型,事实上他们有种类是一个实现的细节。 + +We can easily change this and allow `Monster` to return its `Breed`: + +我们可以很容易的改变这点,允许`Monster`返回`Breed`: + + + +^code 11 + + + +Doing this changes the design of `Monster`. The fact that all monsters have +breeds is now a publicly visible part of its API. There are benefits with either +choice. + +这样做改变了`Monster`的设计。事实是所有这些由品种的怪物都是API的可见部分了,下面是这两者的好处: + + * **If the type object is encapsulated:** + + * **如果类型对象是封装的:** + + * *The complexity of the Type Object pattern is hidden from the rest of + the codebase.* It becomes an implementation detail that only the typed + object has to worry about. + + * *类型对象模式的复杂性对代码库的其他部分是隐藏的。*它成为了一种实现细节,只有有类型的对象需要考虑。 + + * *The typed object can selectively override behavior from the type + object*. Let's say we wanted to change the monster's attack string when + it's near death. Since the attack string is always accessed through + `Monster`, we have a convenient place to put that code: + + * *有联系的对象可以选择新的重载类型对象的行为*。假设我们想要怪物在它解决死亡的时候改变它的攻击字符串。由于攻击字符串总是通过`Monster`获取的,我们有一个方便的地方放置代码: + + ^code 12 + + If outside code was calling `getAttack()` directly on the breed, we + wouldn't have the opportunity to insert that logic. + + 如果外部代码直接在种类上调用`getAttack()`,我们就没有机会能插入逻辑。 + + * *We have to write forwarding methods for everything the type object + exposes.* This is the tedious part of this design. If our type object + class has a large number of methods, the object class will have to have + its own methods for each of the ones that we want to be publicly + visible. + + * *我们的为每件类型对象暴露的方法写转发。*这是这个设计的冗长之处。如果类型对象有很多方法,对象类也得为每一个建立它自己的公共可见方法。 + + * **If the type object is exposed:** + + * **如果类型对象是暴露的:** + + * *Outside code can interact with type objects without having an instance + of the typed class.* If the type object is encapsulated, there's no way + to use it without also having a typed object that wraps it. This + prevents us, for example, from using our constructor pattern where new + monsters are created by calling a method on the breed. If users can't + get to breeds directly, they wouldn't be able to call it. + + * *外部代码可以与类型对象直接交互,无需拥有一个类型对象的实例。*如果类型对象是封装的,如果没有一个包装它的有类型对象就没法使用它。这防止我们,举个例子,在调用种类的方法来创建一个新的怪物时使用构造器模式。如果用户不能直接获得种类,他们就没办法调用它。 + + * *The type object is now part of the object's public API.* In general, + narrow interfaces are easier to maintain than wide ones -- the less you + expose to the rest of the codebase, the less complexity and maintenance + you have to deal with. By exposing the type object, we widen the + object's API to include everything the type object provides. + + * *类型对象现在是对象的公共API的一部分了。*大体上,狭窄的接口较宽的接口更容易掌控——你暴露给代码库其他部分的越少,你需要处理的复杂度和维护就越少。通过暴露类型对象,我们扩宽了对象的API来包含任何类型对象提供的。 + +### How are typed objects created? + +### 有类型的对象是如何创建的? + +With this pattern, each "object" is now a pair of objects: the main object and +the type object it uses. So how do we create and bind the two together? + +使用这个模式,每一个“对象”现在都是一对对象:主对象和它用的类型对象。所以我们怎样创建并绑定两者呢? + + * **Construct the object and pass in its type object:** + + * **构造对象然后传入类型对象:** + + * *Outside code can control allocation.* Since the calling code is + constructing both objects itself, it can control where in memory that + occurs. If we want our objects to be usable in a variety of different + memory scenarios (different allocators, on the stack, etc.) this gives + us the flexibility to do that. + + * *外部代码可以控制分配。*由于调用代码也是构建对象的代码,他可以控制它放到内存的何处。乳沟我们想要我们的UI想在很多内存场景中使用(不同的分配器,在栈中,等等),这给了我们完成它的灵活度。 + + * **Call a "constructor" function on the type object:** + + * **在类型对象上调用一个“构造器”函数:** + + * *The type object controls memory allocation.* This is the other side of + the coin. If we *don't* want users to choose where in memory our objects + are created, requiring them to go through a factory method on the type + object gives us control over that. This can be useful if we want to + ensure all of our objects come from a certain Object Pool or other memory allocator. + + * *类型对象控制了内存分配。*这是硬币的另一面。如果我们*不想*让玩家选择他们在内存中何处创建对象,需要他们在类型对象上调用工厂方法给了我们控制它的权利。在我们想抱着所有的对象都来自一个具体的对象池或者其他的内存分配器也有用。 + +### Can the type change? + +### 类型能改变吗? + +So far, we've presumed that once an object is created and bound to its type +object that that binding will never change. The type an object is created with +is the type it dies with. This isn't strictly necessary. We *could* allow an +object to change its type over time. + +到目前为止,我们假设一旦对象创建并绑定到类型对象上,这种永远不会改变。对象创建时的类型就是他死亡时的类型。这没有那么必要。我们*可以*允许一个对象随着时间改变它的类型。 + +Let's look back at our example. When a monster dies, the designers tell us +sometimes they want its corpse to become a reanimated zombie. We could implement +this by spawning a new monster with a zombie breed when a monster dies, but +another option is to simply get the existing monster and change its breed to a +zombie one. + +让我们看看我们的例子。当怪物死去,设计者告诉我们有时它的尸体会复活成僵尸。我们可以通过在怪物死亡时产生新的有僵尸类型的怪兽,但另一个选项是拿到现有的怪物然后将它的种类改为僵尸。 + + * **If the type doesn't change:** + + * **如果类型不改变:** + + * *It's simpler both to code and to understand.* At a conceptual level, + "type" is something most people probably will not expect to change. This + codifies that assumption. + + * *编码和理解都更容易。*在概念上,“类型”是大多数人不期望改变的东西。这改变了那个假设。 + + * *It's easier to debug.* If we're trying to track down a bug where a + monster gets into some weird state, it simplifies our job if we can take + for granted that the breed we're looking at *now* is the breed the + monster has always had. + + * *更容易查漏。*如果再怪物进入一个奇怪状态的时候我们试图追踪漏洞,如果我们知道我们*现在*看到的种类就是怪物一直有的种类可以大大简化工作。 + + * **If the type can change:** + + * **如果类型可以改变:** + + * *There's less object creation.* In our example, if the type can't + change, we'll be forced to burn CPU cycles creating a new zombie + monster, copying over any attributes from the original monster that need + to be preserved, and then deleting it. If we can change the type, + all that work gets replaced by a simple assignment. + + * *需要创建的对象更少。*在我们的例子中,如果类型不能改变,我们需要消耗CPU循环创建新的僵尸怪物对象,把原先对象中需要保留的属性都拷贝过来,然后删除它。乳沟我们可以改变类型,所有的工作都被一个简单的声明替代。 + + * *We need to be careful that assumptions are met.* There's a fairly tight + coupling between an object and its type. For example, a breed might + assume that a monster's *current* health is never above the starting + health that comes from the breed. + + * *我们需要小心符合假设。*在对象和它的类型间有强耦合是很自然的事情。举个例子,一个种类也许假设怪物*当前的*血量永远高于种类中的初始血量。 + + If we allow the breed to change, we need to make sure that the new + type's requirements are met by the existing object. When we change the + type, we will probably need to execute some validation code to make sure + the object is now in a state that makes sense for the new type. + + 如果我们允许种类改变,我们需要确保新类型的需求被已存对象满足。但我们改变类型,我们也许需要执行一些验证代码保证对象现在的状态对新类型是有意义的。 + +### What kind of inheritance is supported? + +### 它支持何种继承? + + * **No inheritance:** + + * **没有继承:** + + * *It's simple.* Simplest is often best. If you don't have a ton of data + that needs sharing between your type objects, why make things hard on + yourself? + + * *简单。*最简单的通常是最好的。如果你在类型对象间没有大量数据共享,为什么要为难自己呢? + + * *It can lead to duplicated effort.* I've yet to see an authoring system + where designers *didn't* want some kind of inheritance. When you've got + fifty different kinds of elves, having to tune their health by changing + the same number in fifty different places *sucks*. + + * *这会带来重复的工作。*我从未见过看到设计者*不*想要继承的编码系统。当你有十五种不同的精灵时,协调血量就要修改十五处同样的数字糟透了。 + + * **Single inheritance:** + + * **单一继承:** + + * *It's still relatively simple.* It's easy to implement, but, more + importantly, it's also pretty easy to understand. If non-technical users + are going to be working with the system, the fewer moving parts, the + better. There's a reason a lot of programming languages only support + single inheritance. It seems to be a sweet spot between power and + simplicity. + + * *这还是相对简单。*它易于实现,但是,更重要的是,它也易于理解。如果非技术用户正在使用这个系统,移动的部分越少越好。这就是很多编程语言只支持单继承的原因。这看起来是能力和简洁之间的平衡点。 + + * *Looking up attributes is slower.* To get a given piece of data from a + type object, we might need to walk up the inheritance chain to find + the type that ultimately decides the value. If we're in + performance-critical code, we may not want to spend time on this. + + * *查询属性更慢。*为了从一个类型对象中获取一块数据,我们也许需要回溯继承链寻找哪一个类型最终决定了值。如果我们在性能攸关的代码桑,我们也许不想花时间在这上面。 + + * **Multiple inheritance:** + + * **多重继承:** + + * *Almost all data duplication can be avoided.* With a good multiple + inheritance system, users can build a hierarchy for their type objects + that has almost no redundancy. When it comes time to tune numbers, we + can avoid a lot of copy and paste. + + * *几乎可以避免所有的代码重复。*通过一个好的多继承系统,用户可以为他们的类型对象建立几乎没有冗余的层次。但是改变数字的时候,我们可以避免很多复制和粘贴。 + + * *It's complex.* Unfortunately, the benefits for this seem to be more + theoretical than practical. Multiple inheritance is hard to understand + and reason about. + + * *复杂。* 不幸的是,它的好处看上去更多的是理论上的而非实际。多重继承很难理解。 + + If our Zombie Dragon type inherits both from Zombie and Dragon, which + attributes come from Zombie and which come from Dragon? In order to use + the system, users will need to understand how the inheritance graph is + traversed and have the foresight to design an intelligent hierarchy. + + 如果我们的僵尸龙类型继承僵尸和龙,哪些属性来自僵尸,哪些来自于龙?为了使用系统,用户要理解如何横跨继承图并由设计一个优秀层次的愿景。 + + Most C++ coding standards I see today tend to ban multiple inheritance, + and Java and C# lack it completely. That's an acknowledgement of a sad + fact: it's so hard to get it right that it's often best to not use it at + all. While it's worth thinking about, it's rare that you'll want to use + multiple inheritance for the type objects in your games. As always, + simpler is better. + + 我看到的大多数C++编码标准趋向于禁止多重继承,Java和C#完全移除了它。这承认了一个悲伤的事实:它太难掌握了,最好根本不要用。尽管它值得考虑,但很少你想要在类型对象上实现多重继承。就像往常一样,简单的总是最好的。 + +## See Also + +## 参见 + + * The high-level problem this pattern addresses is sharing data and behavior + between several objects. Another pattern that addresses the same problem in + a different way is Prototype. + + * 这个模式处理的高层问题是子啊多个对象间分享数据和行为。另一个用另一种方式解决了相同问题的模式是原型模式。 + + * Type Object is a close cousin to Flyweight. Both let you share data across + instances. With Flyweight, the intent is on saving memory, and the shared + data may not represent any conceptual "type" of object. With the Type Object + pattern, the focus is on organization and flexibility. + + * 类型对象是享元模式的近亲。两者都让你在实例间分享代码。使用享元,意图是节约内存,而分享的数据也许不代表任何概念上的对象的“类型”使用类型对象模式,注意力集中在组织和灵活性。 + + * There's a lot of similarity between this pattern and the State pattern. Both patterns let + an object delegate part of what defines itself to another object. With a + type object, we're usually delegating what the object *is*: invariant data + that broadly describes the object. With State, we delegate what an object + *is right now*: temporal data that describes an object's current + configuration. + + * 这个模式和状态模式有很多相似之处。两者都让它的部分委托给另外一个对象。拖过类型对象,我们通常委托对象*是*什么:不变的数据概括描述对象。通过状态,我们委托了对象*现在是什么*:暂时的数据描述对象当前的设置。 + + When we discussed having an object change its type, you can look at that as + having our Type Object serve double duty as a State too. + + 当我们讨论一个对象改变它的类型时,你可以将其认为类型对象负起了和状态相似的职责。 diff --git a/book/update-method.markdown b/book/update-method.markdown new file mode 100644 index 0000000..20b8943 --- /dev/null +++ b/book/update-method.markdown @@ -0,0 +1,797 @@ +^title Update Method +^section Sequencing Patterns + +## Intent + +## 意图 + +*Simulate a collection of independent objects by telling each to process one +frame of behavior at a time.* + +*通过每次处理一帧的行为模拟一系列独立对象。* + +## Motivation + +## 动机 + +The player's mighty valkyrie is on a quest to steal glorious jewels from where +they rest on the bones of the long-dead sorcerer-king. She tentatively +approaches the entrance of his magnificent crypt and is attacked by... +*nothing*. No cursed statues shooting lightning at her. No undead warriors +patrolling the entrance. She just walks right in and grabs the loot. Game over. You +win. + +玩家控制强壮的瓦尔基里正在从死亡巫王的栖骨之处偷走华丽珠宝的考验中。它尝试接近他华丽的地宫门口,然后遭到了……*没有东西*。没有诅咒雕像向她射击,没有不死战士巡逻入口。她直接走进去然后拿走了珠宝。游戏结束。你赢了。 + +Well, that won't do. + +好吧,这可不行。 + +This crypt needs some guards -- enemies our brave heroine can grapple with. +First up, we want a re-animated skeleton warrior to +patrol back and forth in front of the door. If you ignore everything you +probably already know about game programming, the simplest possible code to make +that skeleton lurch back and forth is something like: + +地宫需要守卫——我们勇敢的英雄可以战斗的敌人。首先,我们需要一个骷髅战士在门口前后移动巡逻。如果你忽视了你已知的关于游戏编程的任何事情,最简单的实现代码让骷髅蹒跚着来回移动大概是这样的: + + + +^code just-patrol + +The problem here, of course, is that the skeleton moves back and forth, but the +player never sees it. The program is locked in an infinite loop, which is not +exactly a fun gameplay experience. What we actually want is for the skeleton to +move one step *each frame*. + +这里的问题,当然,是骷髅来回打转,但是玩家永远看不到。玩家被锁死在一个无限循环,那不是一个有趣的游戏体验。我们事实上想要的是骷髅*每帧*移动一步。 + +We'll have to remove those loops and rely on the outer game loop for iteration. That ensures the game keeps +responding to user input and rendering while the guard is making his rounds. +Like: + +我们得移除这些循环,依赖外层循环来遍历。这保证了游戏在卫士来回时能回应玩家的输入和渲染。 + + + +^code patrol-in-loop + +I did the before/after here to show you how the code gets more complex. +Patrolling left and right used to be two simple `for` loops. It kept track of +which direction the skeleton was moving implicitly by which loop was executing. +Now that we have to yield to the outer game loop each frame and then resume +where we left off, we have to track the direction explicitly using that +`patrollingLeft` variable. + +我在这里之前/之后的做的事展示了代码是如何变得复杂的。左右巡逻需要两个简单的`for`循环。这通过那个循环在执行显式追踪了骷髅在移向哪个方向。现在我们每帧继续为目的外层游戏循环,然后继续我们所做的,我们就使用了`patrollingLeft`显式地追踪了方向。 + +But this more or less works, so we keep going. A brainless bag of bones doesn't +give yon Norse maiden too much of a challenge, so the next thing we add is a +couple of enchanted statues. These will fire bolts of lightning at her every so +often to keep her on her toes. + +但这或多或少的有效,所以我们继续。一个无脑的骨头袋不会给你的挪威少女太多挑战,所以我们添加的下一个是一些魔法雕像。它们一直会向她发射闪电这样让她经常移动。 + +Continuing our, "what's the simplest way to code this" style, we end up with: + +继续我们的“使用最简单的方式编码”的风格,我们得到了: + +^code statues + +You can tell this isn't trending towards code we'd enjoy maintaining. We've got +an increasingly large pile of variables and imperative code all stuffed in the +game loop, each handling one specific entity in the game. To get them all up and +running at the same time, we've mushed their code +together. + +你可以发现这不像是我们能控制的代码了。我们得到了不断增长的变量数目,必要的代码都在游戏循环中,每一个都处理一个特别的游戏实体。为了一起得到他们然后一起运行,我们将他们的代码混杂在了一起。 + + + +The pattern we'll use to fix this is so simple you probably have it in mind +already: *each entity in the game should encapsulate its own behavior.* This +will keep the game loop uncluttered and make it easy to add and remove entities. + +我们用了修复这个的模式非常简单,你也许已经知道了:*每个游戏实体应该封装它自己的行为。*这会保持游戏循环整洁而容易的添加和移除实体。 + +To do this, we need an *abstraction layer*, and we create that by defining an +abstract `update()` method. The game loop maintains a collection of objects, but +it doesn't know their concrete types. All it knows is that they can be updated. +This separates each object's behavior both from the game loop and from the other +objects. + +为了到达这一点,我们需要一个*抽象层*,我们通过定义一个抽象`update()`方法来完成。游戏循环管理一系列对象,但是不知道他们实际的类型。它只知道他们可以被更新。浙江每个对象的行为和游戏循环,和其他对象分离开来。 + +Once per frame, the game loop walks the collection and calls `update()` on each +object. This gives each one a chance to perform one frame's worth of behavior. +By calling it on all objects every frame, they all behave simultaneously. + +每一帧,游戏循环遍历集合,在每个对象上调用`update()`。这给了我们一个机会在每一帧上使用行为。通过在所有对象上每一帧调用它,他们同时行动。 + + + +The game loop has a dynamic collection of objects, so adding and removing them +from the level is easy -- just add and remove them from the collection. Nothing +is hardcoded anymore, and we can even populate the level using some kind of data +file, which is exactly what our level designers want. + +游戏循环有一个动态的对象集合,所以从关卡添加和移除是很容易的——只需要将他们从集合中添加和移除。没有什么硬编码了,我们仍然可以用一些数据文件构成这个关卡,那就是我们关卡设计者需要的。 + +## The Pattern + +## 模式 + +The **game world** maintains a **collection of objects**. Each object implements +an **update method** that **simulates one frame** of the object's behavior. Each +frame, the game updates every object in the collection. + +**游戏世界**管理**一系列对象**。每个对象实现一个**更新方法**模拟对象**一帧**的行为。每一帧,游戏更新集合中的每一个对象。 + +## When to Use It + +## 何时使用 + +If the Game Loop pattern is the +best thing since sliced bread, then the Update Method pattern is its butter. A wide swath of +games featuring live entities that the player interacts with use this pattern in +some form or other. If the game has space marines, dragons, Martians, ghosts, or +athletes, there's a good chance it uses this pattern. + +如果一个游戏循环模式是切片面包,那么更新方法模式就是他的奶油。玩家交换的很多游戏活动实体都用这样或那样的方式实现了这个模式。如果游戏有太卡陆战队,火龙,火星人,鬼魂或者运动员,很有可能它使用了这个模式。 + +However, if the game is more abstract and the moving pieces are less like living +actors and more like pieces on a chessboard, this pattern is often a poor fit. +In a game like chess, you don't need to simulate all of the pieces concurrently, +and you probably don't need to tell the pawns to update +themselves every frame. + +但是游戏如果更加抽象,移动部分不太像活动的角色而更加像棋盘,这个模式通常是一个糟糕的选择。在一个像棋类的游戏,你不需要同时模拟所有的部分,你可能也不需要跳起每帧都更新他们自己。 + + + +Update methods work well when: + +更新方法适应以下情况: + +* Your game has a number of objects or systems that need to run simultaneously. + +* 你的游戏有很多对象或系统需要同时运行。 + +* Each object's behavior is mostly independent of the others. + +* 每个对象的行为都与其他的大部分独立。 + +* The objects need to be simulated over time. + +* 对象需要随时间模拟。 + +## Keep in Mind + +## 记住 + +This pattern is pretty simple, so there aren't a lot of hidden surprises in its +dark corners. Still, every line of code has its ramifications. + +这个模式很简单,所以没有太多黑暗角落。但是每行代码还是有分叉。 + +### Splitting code into single frame slices makes it more complex + +## 将代码分割成一帧的片让它更加复杂 + +When you compare the first two chunks of code, the second is a good bit more +complex. Both simply make the skeleton guard walk back and forth, but the second +one does this while yielding control to the game loop each frame. + +当你比较前面两块代码的时候,第二块看上去更加复杂。两者都简单的让骷髅守卫来回移动,但是第二个做的同时将每帧控制权交给了游戏循环。 + +That change is almost always necessary to handle +user input, rendering, and the other stuff that the game loop takes care of, so +the first example wasn't very practical. But it's worth keeping in mind that there's +a big up front complexity cost when you julienne your behavioral code like this. + +这个改变几乎总是需要处理用户输入,渲染和其他游戏循环需要注意的事项,所以第一个例子不是太实用。但是很有必要记住在将你的行为切片时会像这样增加很高的复杂性。 + + + +### You have to store state to resume where you left off each frame + +### 当你离开每一帧的时候,你需要存储状态来继续。 + +In the first code sample, we didn't have any variables to indicate whether the +guard was moving left or right. That was implicit based on which code was +currently executing. + +在第一个代码的例子中,我们不需要用任何变量表面守卫在向左还是向右移动。这显式的依赖于哪块代码正在运行。 + +When we changed this to a one-frame-at-a-time form, we had to create a +`patrollingLeft` variable to track that. When we return out of the code, the +execution position is lost, so we need to explicitly store enough information to +restore it on the next frame. + +当我们将其变为一次一帧的形式,我们需要创建一个`patrollingLeft`变量来追踪那个。但我从代码中返回时,执行位置就丢失了,所以我们需要显式存储足够的信息为了下帧恢复。 + +The State pattern can often help here. +Part of the reason state machines are common in games is because (like their +name implies) they store the kind of state that you need to pick up where you +left off. + +状态模式通常可以帮助解决这里。状态机在游戏中频繁的原因的一部分时(就像他们名字暗示的)在你离开的时候,他们为你存储你需要的各种状态。 + +### Objects all simulate each frame but are not truly concurrent + +### 对象都模拟每一帧但不是真的同步的 + +In this pattern, the game loops over a collection of objects and updates each +one. Inside the `update()` call, most objects are able to reach out and touch +the rest of the game world, including other objects that are being updated. This +means the *order* in which the objects are updated is significant. + +在这个模式中,游戏循环了一堆对象,更新每一个。在`update()`调用中,大多数对象都能够接触到游戏世界的其他部分,包括按下正在被更新的。这就意味着你更新对象的*顺序*至关重要。 + +If A comes before B in the list of objects, then when A updates, it will see B's +previous state. But when B updates, it will see A's *new* state, since A has already been updated this +frame. Even though from the player's perspective, everything is moving at the +same time, the core of the game is still turn-based. It's just that a complete +"turn" is only one frame long. + +如果对象列表中,A在B之前,难免当A更新的时候,它会看到B之前的状态。但是当B更新时,由于A已经在这帧更新了,它会看见A的*新*状态。哪怕从玩家角度,所有对象都是同时移动的,游戏的核心还是基于回合的。只是一个完整的“回合”只有一帧那么长。 + + + +This is mostly a good thing as far as the game logic is concerned. Updating +objects in parallel leads you to some unpleasant semantic corners. Imagine a +game of chess where black and white moved at the same time. They both try to +make a move that places a piece in the same currently empty square. How should +this be resolved? + +当关注游戏逻辑时,这通常是好事情。平行更新游戏带你到一些不愉快的语义角落。想象国际象棋如果黑白同时移动会发生什么。他们都试图同时往现在的空格子中放置棋子。这怎么解决? + +Updating sequentially solves this -- each update +incrementally changes the world from one valid state to the next with no period +of time where things are ambiguous and need to be reconciled. + +序列更新解决了这一点——每一次更新让游戏世界从一个合法状态增量更新到下一个,没有引起歧义而需要协调的部分。 + + + +### Be careful modifying the object list while updating + +### 在更新时小心修改对象列表 + +When you're using this pattern, a lot of the game's behavior ends up nestled in +these update methods. That often includes code that adds or removes updatable +objects from the game. + +当你使用这个模式的时候,很多游戏行为在他们的更新方法中纠缠子啊一起。这通常包括增加和删除可更新对象。 + +For example, say a skeleton guard drops an item when slain. With a new object, +you can usually add it to the end of the list without too much trouble. +You'll keep iterating over that list and eventually get to the new one at the +end and update it too. + +就开了张,假设骷髅守卫在被杀死的时候掉落东西。使用一个新对象,你通常可以将其增加到列表尾部不引起任何问题。你会继续遍历这张链表,最终盗了新的那个然后也更新了它。 + +But that does mean that the new object gets a chance to act during the frame +that it was spawned, before the player has even had a chance to see it. If you +don't want that to happen, one simple fix is to cache the number of objects in +the list at the beginning of the update loop and only update that many before +stopping: + +但是这表明新对象在它生产的那一帧就有机会活动,甚至在玩家看到它之前。如果你不想发生那种情况,一个简单的修复方法就是在游戏循环中缓存列表对象的数目然后在停止前只更新那么多。 + +^code skip-added + +Here, `objects_` is an array of the updatable objects in the game, and +`numObjects_` is its length. When new objects are added, it gets incremented. We +cache the length in `numObjectsThisTurn` at the beginning of the loop so that +the iteration stops before we get to any new objects added during the current +frame. + +这儿,`objects_`是一数组游戏中的可更新对象,而`numObjects_`是它的长度。当新对象添加时,它增加。我们在循环的开头在`numObjectsThisTurn`缓存它的长度,这样在更新帧时添加任何新对象后,循环会停在之前的位置。 + +A hairier problem is when objects are *removed* while iterating. You vanquish +some foul beast and now it needs to get yanked out of the object list. If it +happens to be before the current object you're updating in the list, you can +accidentally skip an object: + +一个更麻烦的问题是当对象在遍历时*移除*。你击败了一些邪恶的野兽,现在它需要移出对象列表。如果它正好是你更新状态对象之前的对象,你会意外的跳过一个对象: + +^code skip-removed + +This simple loop increments the index of the object being updated each +iteration. The left side of the illustration below shows what the array looks +like while we're updating the heroine: + +这个简单的循环每一次迭代都添加一个对象。下面插图的左侧展示了在我们更新英雄的时候数组看上去是什么样的: + +A list of entities during a removal. A pointer points to the second entity, Heroine. After the Foul Beast above it is removed, the pointer moves down while the Heroine moves up. + +Since we're updating her, `i` is 1. She slays the foul beast so it gets removed +from the array. The heroine shifts up to 0, and the hapless peasant shifts up to +1. After updating the heroine, `i` is incremented to 2. As you can see on the +right, the hapless peasant is skipped over and +never gets updated. + +由于我们在更新她,`i`是1。邪恶野兽被她杀了,因此需要从数组移除。英雄移到了位置0,倒霉的乡下人移到了1。在更新英雄之后,`i`增加到了2,。就像你在右边看到的,倒霉的乡下人被跳过了,没有被更新。 + + + +One fix is to just be careful when you remove objects and update any iteration +variables to take the removal into account. Another is to defer removals until +you're done walking the list. Mark the object as "dead", but leave it in place. +During updating, make sure to skip any dead objects. Then, when that's done, walk the list again to remove the corpses. + +一个结局方案是在考虑移除对象和需要被移除物的迭代变量。另一个是在遍历完列表后在移除。将对象标标为“死亡”,但是把它放在那里。在更新时跳过任何死亡的对象。然后,在完成时,在此遍历列表删除尸体。 + + + +## Sample Code + +## 实例代码。 + +This pattern is so straightforward that the sample code almost belabors the point. +That doesn't mean the pattern isn't *useful*. It's useful in part *because* it's +simple: it's a clean solution to a problem without a lot of ornamentation. + +这个模式太直观了,代码几乎只是在重新讨论要点。这不意味着模式不*有用*。它*因为*简单而有用:这是一个无需装饰的干净解决方案。 + +But to keep things concrete, let's walk through a basic implementation. +We'll start with an `Entity` class that will represent the skeletons and statues: + +但是为了让事情具体,让我回顾一些基础的实现。我们会代表骷髅和雕像的`Entity`类开始: + +^code entity-class + +I stuck a few things in there, but just the bare minimum we'll need later. +Presumably in real code, there'd be lots of other stuff like graphics and +physics. The important bit for this pattern is that it has an abstract +`update()` method. + +我在这里塞了一些东西,但是还是几乎我们等会需要的最少量。可以推断在真实代码中,这会有很多其他东西比如图形和物理。这个模式重要的部分是它有一个抽象的`update()`方法。 + +The game maintains a collection of these entities. In our sample, we'll put that +in a class representing the game world: + +游戏管理一系列实体。在我们的示例中,我会把它放在一个代表游戏世界的类中。 + + + +^code game-world + + + +Now that everything is set up, the game implements the pattern by updating each +entity every frame: + +现在,所有都设好了,游戏通过每帧更新每个实体来实现模式。 + + + +^code game-loop + + + +### Subclassing entities?! + +### 子类化实体?! + +There are some readers whose skin is crawling right now because I'm using +inheritance on the main `Entity` class to define different behaviors. If you don't +happen to see the problem, I'll provide some context. + +这里有很多读者刚刚起了鸡皮疙瘩,以为我在主要的`Entity`类中使用了继承来定义不同的行为。如果你在这里没有看出问题,我会提供一些线索。 + +When the game industry emerged from the primordial seas of 6502 assembly code +and VBLANKs onto the shores of object-oriented languages, developers went into a +software architecture fad frenzy. One of the biggest was using inheritance. +Towering, Byzantine class hierarchies were built, big enough to blot out the +sun. + +当游戏工业从原始的6502汇编代码和和VBLANKs的原始海洋中浮现,转换为面向对象的于洋,开发者陷入了软件架构的时尚狂热。其中之一就是使用继承。高耸的拜占庭式对象层次被建立了,足够遮挡住太阳。 + +It turns out that was a terrible idea and no one can maintain a giant class hierarchy without it crumbling around them. Even +the Gang of Four knew this in 1994 when they wrote: + +这证明是一个糟糕的主要,没人可以不打破他们来管理一个巨大的对象层次。哪怕GoF在1994都知道这一点,写下了: + +> Favor 'object composition' over 'class inheritance'. + +> 多用“对象组合”,而非“类继承”。 + + + +When this realization percolated through the game industry, the solution that +emerged was the Component pattern. +Using that, `update()` would be on the entity's *components* and not on `Entity` +itself. That lets you avoid creating complicated class hierarchies of entities +to define and reuse behavior. Instead, you just mix and match components. + +当这种认知渗透了游戏工业,解决发难是使用组件模式。使用它,`update()`是实体的*组件*而不是在`Entity`中。这让你回避了创建实体的复杂类继承层次并重用行为。相反,你只需混合和组装组件。 + +If I were making a real game, I'd probably do that too. But this chapter isn't about components. It's about `update()` +methods, and the simplest way I can show them, with as few moving parts as +possible, is by putting that method right on `Entity` and making a few +subclasses. + +如果我在做真实的游戏,我也许会按摩做,但是这一章不是关于组件的。而是关于`update()`方法,我展示他们的最简单的方法,使用尽可能少的部分,就是把方法放在`Entity`中然后创建一些子类。 + + + +### Defining entities + +### 定义实体 + +OK, back to the task at hand. Our original motivation was to be able to define a +patrolling skeleton guard and some lightning-bolt-unleashing magical statues. +Let's start with our bony friend. To define his patrolling behavior, we make a +new entity that implements `update()` appropriately: + +好了,回到手头的任务。我们原先的动机是能够定义一个巡逻的骷髅守卫和释放闪电的魔法雕像。让我们从我们的骨头朋友开始。为了定义它的巡逻行为,我们定义一个新的实体恰当地实现了`update()`: + +^code skeleton + +As you can see, we pretty much just cut that chunk of code from the game loop +earlier in the chapter and pasted it into `Skeleton`’s `update()` method. +The one minor difference is that `patrollingLeft_` has been made into a field +instead of a local variable. That way, its value sticks around between calls to +`update()`. + +就像你看到的那样,我们几乎就是从早先的游戏循环中剪切了代码然后粘贴到`Skeleton`的`update()`方法中。唯一的小小不同是`patrollingLeft_`没定义为字段而不是本地变量。通过这种发那个还是,它的值在对`update()`间保持不变。 + +Let's do this again with the statue: + +让我们通过对象重新做一遍: + +^code statue + +Again, most of the change is moving code from the game loop into the class +and renaming some stuff. In this case, though, we've actually made the codebase +simpler. In the original nasty imperative code, there were separate local +variables for each statue's frame counter and rate of fire. + +又一次,大部分改动是将代码从游戏循环移动到类中然后重命名一些东西。但是,在这个例子中,我们真的让代码库变简单了。在原先讨厌的命令式代码中,有分散的本地变量存储每个雕像帧计数器和开火的速率。 + +Now that those have been moved into the `Statue` class itself, you can create as +many as you want and each instance will have its own little timer. That's really +the motivation behind this pattern -- it's now much easier to add new entities to +the game world because each one brings along everything it needs to take care of +itself. + +现在那些都被移动到了`Statue`类中,你可以想创建多少就创建多少,每一个实例都有它自己的小计时器。这是这章别后的真实动机——现在为游戏世界增加新实体会更加简单,以为每一个都带来了需要照顾它的全部东西。 + +This pattern lets us separate *populating* the game world from *implementing* +it. This in turn gives us the flexibility to populate the world using something +like a separate data file or level editor. + +这个模式让我们分离了*构建*游戏世界和*实现*它。这同样给了我们使用分散的数据文件或关卡编辑器来构建世界的灵活性。 + + + +A UML diagram. World has a collection of Entities, each of which has an update() method. Skeleton and Statue both inherit from Entity. + + + +### Passing time + +### 传递时间 + +That's the key pattern, but I'll just touch on a common refinement. So far, +we've assumed every call to `update()` advances the state of the game world by +the same fixed unit of time. + +这是关键的模式,但是我只涉及了一个常用细化。到目前为止,我们假设每一次对`update()`的调用都推动游戏世界前进了一个固定的时间。 + +I happen to prefer that, but many games use a *variable +time step*. In those, each turn of the game loop may simulate a larger or +smaller slice of time depending on how long it took to process and render the +previous frame. + +我恰好喜欢那样,但是很逗优势使用*可变时间步长*。在那些中,每一次游戏循环都可能模拟一个大的或小的时间片段,就与它需要多长时间处理和渲染前一帧。 + + + +That means that each `update()` call needs to know how far the hand of the +virtual clock has swung, so you'll often see the elapsed time passed in. For +example, we can make our patrolling skeleton handle a variable time step like +so: + +这意味着每一次`update()`调用都需要知道虚拟的时钟转动了多少,所以你经常可以看到传入消逝的时光。举个例子,我们可以让我们的巡逻骷髅处理一个变化的时间步长,就像这样: + +^code variable + +Now, the distance the skeleton moves increases as the elapsed time grows. You +can also see the additional complexity of dealing with a variable time step. The +skeleton may overshoot the bounds of its patrol with a large time slice, and we +have to handle that carefully. + +现在,骷髅移动的距离随着消逝时间的增长而增长。你也可以看到处理变化时间步长所需要的额外复杂度。骷髅也许随着较长的时间步长而移出了巡逻的范围,而我们需要小心的处理那个。 + +## Design Decisions + +## 设计决策 + +With a simple pattern like this, there isn't too much variation, but there are +still a couple of knobs you can turn. + +在这样一个简单的模式中,没有太多的变化,到那时这里仍有一些你可以控制的按钮: + +### What class does the update method live on? + +### 更新方法在哪个类中? + +The most obvious and most important decision you'll make is what class to put +`update()` on. + +最明显和最重要的决策就是你将`update()`放在哪个类中。 + + * **The entity class:** + + * **实体类中:** + + This is the simplest option if you already have an entity class since it + doesn't bring any additional classes into play. This may work if you don't + have too many kinds of entities, but the industry is generally moving away + from this. + + 如果你已经有一个实体类了,这是最简单的选项,因为这不会带来附加的类。乳沟你不需要太多种的实体,这也许科学,但是业界已经逐渐远离他了。 + + Having to subclass `Entity` every time you want a new behavior is brittle + and painful when you have a large number of different kinds. You'll + eventually find yourself wanting to reuse pieces of code in a way that + doesn't gracefully map to a single inheritance hierarchy, and then you're + stuck. + + 当你有很多不同种的类时,一想要新行为时就建一个`Entity`子类是脆弱痛苦的。你最终发现你在用一种非单一继承层次的方法重用代码,然后你就卡住了。 + + * **The component class:** + + * **组件类:** + + If you're already using the Component pattern, this is a no-brainer. It lets each + component update itself independently. In the same way that the Update Method + pattern in general lets you decouple game entities from each other in the + game world, this lets you decouple *parts of a single entity* from each + other. Rendering, physics, and AI can all take care of themselves. + + 如果你已经是使用了组件模式,这是毫无疑问的选择。这让每一个组件独立更新它自己。和更新方法一样在大体上让你互相解耦游戏中的实体,这让你互相解耦了*。单一实体中的各部分*。渲染,物理,AI都可以自顾自了。 + + * **A delegate class:** + + * **委托类:** + + There are other patterns that involve delegating part of a class's behavior + to another object. The State + pattern does this so that you can change an object's behavior by changing + what it delegates to. The Type + Object pattern does this so that you can share behavior across a bunch + of entities of the same "kind". + + 还有其他模式包括了委托一部分类的行为给其他的对象。状态模式这样做了,你就可以改变对象的行为通过改变它委托给谁。类型对象模式也这样做了,这样你可以在同“种”实体间分享行为。 + + If you're using one of those patterns, it's natural to put `update()` on + that delegated class. In that case, you may still have the `update()` method + on the main class, but it will be non-virtual and will simply forward to the + delegated object. Something like: + + 如果你使用这些模式中的一个,将`update()`放在委托类中是很自然的。在那种情况下,也许主类中仍有`update()`方法,但是它不是虚方法,可以简单的委托给委托对象。就像这样: + + ^code forward + + Doing this lets you define new behavior by changing out the delegated + object. Like using components, it gives you the flexibility to change + behavior without having to define an entirely new subclass. + + 这样做让你改变委托对象来定义新行为。就像使用组件,这给了你无须定义完全性的子类就能改变行为的灵活性。 + +### How are dormant objects handled? + +### 如何处理隐藏对象? + +You often have a number of objects in the world that, for whatever reason, +temporarily don't need to be updated. They could be disabled, or off-screen, or +not unlocked yet. If a large number of objects are in this state, it can be a +waste of CPU cycles to walk over them each frame only to do nothing. + +游戏世界中的游戏对象,不管什么原因,临时不需更新。它们可能是停用了,或者超出了屏幕,或者没有解锁。如果状态中的这种对象很多,每帧遍历他们什么都不做就是在浪费CPU循环。 + +One alternative is to maintain a separate collection of just the "live" objects +that do need updating. When an object is disabled, it's removed from the +collection. When it gets re-enabled, it's added back. This way, you only iterate +over items that actually have real work do to. + +一个选择是管理一个单独的“活动”对象集合,那些真的需要更新的。当一个对象停用时,它从那个集合中移除。当它启用时,它被添回来。用这种方式,你只需要迭代那些真正需要的东西: + + * **If you use a single collection containing inactive objects:** + + * **如果你使用一个简单集合包括了所有不活跃对象:** + + * *You waste time*. For inactive objects, you'll + end up either checking some "am I enabled" flag or calling a method that + does nothing. + + * *浪费时间*。对于不活跃对象,你最后要么检查一些“我启用了么”标识或者调用一些啥都不做的方法。 + + + + * **If you use a separate collection of only active objects:** + + * **如果你使用一个单独的活动对象集合:** + + * *You use extra memory to maintain the second collection.* There's still + usually another master collection of all entities for cases where you + need them all. In that case, this collection is technically redundant. + When speed is tighter than memory (which it often is), this can still be + a worthwhile trade-off. + + * *你使用了额外的内存管理第二个集合。*当你需要所有实体的时候这通常又是一个巨大的集合。在那种情况下,这种集合是多余的。在速度比内存更紧张的时候(通常如此),这仍是一个值得的交易。 + + Another option to mitigate this is to have two collections, but have the + other collection only contain the *inactive* entities instead of all of + them. + + 另一个缓和选择是使用两个集合,但是另一个集合只包含他们中的*不活跃*实体而不是他们全部。 + + * *You have to keep the collections in sync.* When objects are created or + completely destroyed (and not just made temporarily inactive), you have + to remember to modify both the master collection and active object one. + + * *你得保持集合同步。*但对象创建或2完全销毁时(不是暂时停用),你得记得修改主要集合和活跃集合。 + +The metric that should guide your approach here is how many inactive objects you +tend to have. The more you have, the more useful it is to have a separate +collection that avoids them during your core game loop. + +指导你方法的度量标准是你可能有多少不活跃对象。你有的越多,用一个分离集合避免在核心游戏循环中用到他们就更有用。 + +## See Also + +## 参见 + + * This pattern, along with Game Loop and Component, is part of a trinity that often forms the nucleus of a game engine. + + * 这个模式,以及游戏循环模式和组件模式,是构建游戏引擎核心的三位一体。 + + * When you start caring about the cache performance of updating a bunch of + entities or components in a loop each frame, the Data Locality pattern can help + make that faster. + + 当你关注在每帧中更新一堆实体或组件的缓存性能时,数据局部性模式可以让它跑到更快。 + + * The [Unity](http://unity3d.com) framework uses this pattern in several + classes, including [`MonoBehaviour`](http://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.Update.html). + + * [Unity](http://unity3d.com)框架在多个类中使用了这个模式,包括[`MonoBehaviour`](http://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.Update.html)。 + + * Microsoft's [XNA](http://creators.xna.com/en-US/) platform uses this pattern + both in the [`Game`](http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.game.update.aspx) + and [`GameComponent`](http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.gamecomponent.update.aspx) + classes. + + * 微软的[XNA](http://creators.xna.com/en-US/)平台在[`Game`](http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.game.update.aspx) + 和[`GameComponent`](http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.gamecomponent.update.aspx)类中使用了这个模式。 + + * The [Quintus](http://html5quintus.com/) JavaScript game engine uses this + pattern on its main [`Sprite`](http://html5quintus.com/guide/sprites.md) + class. + + * [Quintus](http://html5quintus.com/),一个JavaScript游戏引擎在它的主[`Sprite`](http://html5quintus.com/guide/sprites.md)类中使用了这个模式。 diff --git a/build b/build new file mode 100644 index 0000000..ff7e187 --- /dev/null +++ b/build @@ -0,0 +1,2 @@ +#!/bin/bash +python script/format.py $@ diff --git a/code/cpp/bytecode.h b/code/cpp/bytecode.h new file mode 100644 index 0000000..a2bed8d --- /dev/null +++ b/code/cpp/bytecode.h @@ -0,0 +1,363 @@ +#ifndef cpp_bytecode_h +#define cpp_bytecode_h + +#include "common.h" + +namespace Bytecode +{ + namespace Interpreter + { + //^expression + class Expression + { + public: + virtual ~Expression() {} + virtual double evaluate() = 0; + }; + //^expression + + //^number + class NumberExpression : public Expression + { + public: + NumberExpression(double value) + : value_(value) + {} + + virtual double evaluate() + { + return value_; + } + + private: + double value_; + }; + //^number + + //^addition + class AdditionExpression : public Expression + { + public: + AdditionExpression(Expression* left, Expression* right) + : left_(left), + right_(right) + {} + + virtual double evaluate() + { + // Evaluate the operands. + double left = left_->evaluate(); + double right = right_->evaluate(); + + // Add them. + return left + right; + } + + private: + Expression* left_; + Expression* right_; + }; + //^addition + } + + //^magic-api + void setHealth(int wizard, int amount); + void setWisdom(int wizard, int amount); + void setAgility(int wizard, int amount); + //^magic-api + + //^magic-api-fx + void playSound(int soundId); + void spawnParticles(int particleType); + //^magic-api-fx + + void setHealth(int wizard, int amount) {} + void setWisdom(int wizard, int amount) {} + void setAgility(int wizard, int amount) {} + void playSound(int soundId) {} + void spawnParticles(int particleType) {} + int getHealth(int wizard) { return 0; } + int getAgility(int wizard) { return 0; } + int getWisdom(int wizard) { return 0; } + + void increaseHealth() + { + //^increase-health + setHealth(0, getHealth(0) + + (getAgility(0) + getWisdom(0)) / 2); + //^increase-health + } + + //^instruction-enum + enum Instruction + { + INST_SET_HEALTH = 0x00, + INST_SET_WISDOM = 0x01, + INST_SET_AGILITY = 0x02, + INST_PLAY_SOUND = 0x03, + INST_SPAWN_PARTICLES = 0x04 + //^omit + ,INST_LITERAL, + INST_GET_HEALTH, + INST_GET_WISDOM, + INST_GET_AGILITY, + INST_ADD, + //^omit + }; + //^instruction-enum + + static const int SOUND_BANG = 1; + static const int PARTICLE_FLAME = 1; + + void interpretInstruction() + { + Instruction instruction = INST_SET_AGILITY; + + //^interpret-instruction + switch (instruction) + { + case INST_SET_HEALTH: + setHealth(0, 100); + break; + + case INST_SET_WISDOM: + setWisdom(0, 100); + break; + + case INST_SET_AGILITY: + setAgility(0, 100); + break; + + case INST_PLAY_SOUND: + playSound(SOUND_BANG); + break; + + case INST_SPAWN_PARTICLES: + spawnParticles(PARTICLE_FLAME); + break; + //^omit + case INST_LITERAL: + case INST_GET_HEALTH: + case INST_GET_WISDOM: + case INST_GET_AGILITY: + case INST_ADD: + break; + //^omit + } + //^interpret-instruction + } + + namespace NoParams + { + //^vm + class VM + { + public: + void interpret(char bytecode[], int size) + { + for (int i = 0; i < size; i++) + { + char instruction = bytecode[i]; + switch (instruction) + { + // Cases for each instruction... + //^omit + case INST_SPAWN_PARTICLES: + break; + default: + break; + //^omit + } + } + } + }; + //^vm + } + + namespace Stack + { + //^stack + class VM + { + public: + VM() + : stackSize_(0) + {} + + // Other stuff... + + private: + static const int MAX_STACK = 128; + int stackSize_; + int stack_[MAX_STACK]; + }; + //^stack + } + + namespace PushPop + { + //^push-pop + class VM + { + private: + void push(int value) + { + // Check for stack overflow. + assert(stackSize_ < MAX_STACK); + stack_[stackSize_++] = value; + } + + int pop() + { + // Make sure the stack isn't empty. + assert(stackSize_ > 0); + return stack_[--stackSize_]; + } + + // Other stuff... + //^omit + void interpret(); + static const int MAX_STACK = 128; + int stackSize_; + int stack_[MAX_STACK]; + //^omit + }; + //^push-pop + + void VM::interpret() + { + int instruction = INST_SET_AGILITY; + //^pop-instructions + switch (instruction) + { + case INST_SET_HEALTH: + { + int amount = pop(); + int wizard = pop(); + setHealth(wizard, amount); + break; + } + + case INST_SET_WISDOM: + case INST_SET_AGILITY: + // Same as above... + + case INST_PLAY_SOUND: + playSound(pop()); + break; + + case INST_SPAWN_PARTICLES: + spawnParticles(pop()); + break; + } + //^pop-instructions + + char bytecode[123]; + int i = 0; + switch (INST_LITERAL) + { + //^interpret-literal + case INST_LITERAL: + { + // Read the next byte from the bytecode. + int value = bytecode[++i]; + push(value); + break; + } + //^interpret-literal + + //^read-stats + case INST_GET_HEALTH: + { + int wizard = pop(); + push(getHealth(wizard)); + break; + } + + case INST_GET_WISDOM: + case INST_GET_AGILITY: + // You get the idea... + //^read-stats + + //^add + case INST_ADD: + { + int b = pop(); + int a = pop(); + push(a + b); + break; + } + //^add + } + } + } + + namespace TaggedValue + { + //^tagged-value + enum ValueType + { + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING + }; + + struct Value + { + ValueType type; + union + { + int intValue; + double doubleValue; + char* stringValue; + }; + }; + //^tagged-value + } + + namespace ValueOop + { + enum ValueType + { + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING + }; + + //^value-interface + class Value + { + public: + virtual ~Value() {} + + virtual ValueType type() = 0; + + virtual int asInt() { + // Can only call this on ints. + assert(false); + return 0; + } + + // Other conversion methods... + }; + //^value-interface + + //^int-value + class IntValue : public Value + { + public: + IntValue(int value) + : value_(value) + {} + + virtual ValueType type() { return TYPE_INT; } + virtual int asInt() { return value_; } + + private: + int value_; + }; + //^int-value + } +} + +#endif diff --git a/code/cpp/command.h b/code/cpp/command.h new file mode 100644 index 0000000..ad0bc12 --- /dev/null +++ b/code/cpp/command.h @@ -0,0 +1,287 @@ +// +// command.h +// cpp +// +// Created by Bob Nystrom on 10/7/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#ifndef cpp_command_h +#define cpp_command_h + +namespace CommandPattern +{ + enum Button + { + BUTTON_UP, + BUTTON_DOWN, + BUTTON_LEFT, + BUTTON_RIGHT, + BUTTON_X, + BUTTON_Y, + BUTTON_A, + BUTTON_B + }; + + bool isPressed(Button button) { return false; } + void jump() {} + void fireGun() {} + void swapWeapon() {} + void lurchIneffectively() {} + + namespace BeforeCommand + { + class InputHandler + { + public: + void handleInput(); + }; + + //^handle-input + void InputHandler::handleInput() + { + if (isPressed(BUTTON_X)) jump(); + else if (isPressed(BUTTON_Y)) fireGun(); + else if (isPressed(BUTTON_A)) swapWeapon(); + else if (isPressed(BUTTON_B)) lurchIneffectively(); + } + //^handle-input + } + + namespace InputHandlingCommand + { + //^command + class Command + { + public: + virtual ~Command() {} + virtual void execute() = 0; + }; + //^command + + //^command-classes + class JumpCommand : public Command + { + public: + virtual void execute() { jump(); } + }; + + class FireCommand : public Command + { + public: + virtual void execute() { fireGun(); } + }; + + // You get the idea... + //^command-classes + + //^input-handler-class + class InputHandler + { + public: + void handleInput(); + + // Methods to bind commands... + + private: + Command* buttonX_; + Command* buttonY_; + Command* buttonA_; + Command* buttonB_; + }; + //^input-handler-class + + //^handle-input-commands + void InputHandler::handleInput() + { + if (isPressed(BUTTON_X)) buttonX_->execute(); + else if (isPressed(BUTTON_Y)) buttonY_->execute(); + else if (isPressed(BUTTON_A)) buttonA_->execute(); + else if (isPressed(BUTTON_B)) buttonB_->execute(); + } + //^handle-input-commands + } + + namespace CommandedActors + { + class GameActor + { + public: + void jump() {} + }; + + //^actor-command + class Command + { + public: + virtual ~Command() {} + virtual void execute(GameActor& actor) = 0; + }; + //^actor-command + + //^jump-actor + class JumpCommand : public Command + { + public: + virtual void execute(GameActor& actor) + { + actor.jump(); + } + }; + //^jump-actor + + class InputHandler + { + public: + Command* handleInput(); + private: + Command* buttonX_; + Command* buttonY_; + Command* buttonA_; + Command* buttonB_; + }; + + //^handle-input-return + Command* InputHandler::handleInput() + { + if (isPressed(BUTTON_X)) return buttonX_; + if (isPressed(BUTTON_Y)) return buttonY_; + if (isPressed(BUTTON_A)) return buttonA_; + if (isPressed(BUTTON_B)) return buttonB_; + + // Nothing pressed, so do nothing. + return NULL; + } + //^handle-input-return + + void executeCommand() + { + InputHandler inputHandler; + GameActor actor; + //^call-actor-command + Command* command = inputHandler.handleInput(); + if (command) + { + command->execute(actor); + } + //^call-actor-command + use(actor); + } + } + + namespace Undo + { + class Unit { + public: + int x() { return 0; } + int y() { return 0; } + + void moveTo(int x, int y) {} + }; + + namespace UndoBefore + { + class Command + { + public: + virtual ~Command() {} + virtual void execute() = 0; + }; + + //^move-unit + class MoveUnitCommand : public Command + { + public: + MoveUnitCommand(Unit* unit, int x, int y) + : unit_(unit), + x_(x), + y_(y) + {} + + virtual void execute() + { + unit_->moveTo(x_, y_); + } + + private: + Unit* unit_; + int x_, y_; + }; + //^move-unit + + Unit* getSelectedUnit() { return NULL; } + + //^get-move + Command* handleInput() + { + Unit* unit = getSelectedUnit(); + + if (isPressed(BUTTON_UP)) { + // Move the unit up one. + int destY = unit->y() - 1; + return new MoveUnitCommand(unit, unit->x(), destY); + } + + if (isPressed(BUTTON_DOWN)) { + // Move the unit down one. + int destY = unit->y() + 1; + return new MoveUnitCommand(unit, unit->x(), destY); + } + + // Other moves... + + return NULL; + } + //^get-move + } + + namespace UndoAfter + { + //^undo-command + class Command + { + public: + virtual ~Command() {} + virtual void execute() = 0; + virtual void undo() = 0; + }; + //^undo-command + + //^undo-move-unit + class MoveUnitCommand : public Command + { + public: + MoveUnitCommand(Unit* unit, int x, int y) + : unit_(unit), + xBefore_(0), + yBefore_(0), + x_(x), + y_(y) + {} + + virtual void execute() + { + // Remember the unit's position before the move + // so we can restore it. + xBefore_ = unit_->x(); + yBefore_ = unit_->y(); + + unit_->moveTo(x_, y_); + } + + virtual void undo() + { + unit_->moveTo(xBefore_, yBefore_); + } + + private: + Unit* unit_; + int xBefore_, yBefore_; + int x_, y_; + }; + //^undo-move-unit + } + } +} + +#endif diff --git a/code/cpp/common.h b/code/cpp/common.h new file mode 100644 index 0000000..35828e5 --- /dev/null +++ b/code/cpp/common.h @@ -0,0 +1,24 @@ +#pragma once + +// Makes the argument appear to be used so that we don't get an unused +// variable warning for it. Lets us leave that warning on to catch unintended +// unused variables. +template +void use(const T& obj) { + // Do nothing. +} + +#define ASSERT(condition) \ + if (!(condition)) \ + { \ + std::cout << "FAIL: " #condition << "\n" << __FILE__ \ + << ":" << __LINE__ << std::endl; \ + abort(); \ + } + +void assert(bool condition) { + if (!condition) { + printf("WTF\n"); + exit(1); + } +} \ No newline at end of file diff --git a/code/cpp/component.h b/code/cpp/component.h new file mode 100644 index 0000000..18d4633 --- /dev/null +++ b/code/cpp/component.h @@ -0,0 +1,630 @@ +#include + +#include "common.h" + +enum Joystick +{ + DIR_NONE, + DIR_LEFT, + DIR_RIGHT +}; + +class Controller +{ +public: + static Joystick getJoystickDirection() + { + // Determine which direction the user is currently + // pressing on the joystick... + return DIR_NONE; + } +}; + +class Sprite +{ +}; + +class Graphics +{ +public: + void draw(Sprite& sprite, int x, int y) + { + // Draw the sprite at the given position... + } +}; + +class Volume +{ +}; + +class World +{ +public: + void resolveCollision(Volume& volume, int& x, int& y, int& velocity) + { + // Determine what the hero is colliding with and + // modify position and velocity if needed... + } +}; + +namespace Motivation +{ + bool collidingWithFloor() { return false; } + static const int INVISIBLE = 1; + int getRenderState() { return INVISIBLE; } + void playSound(int sound) {} + static const int HIT_FLOOR = 1; + + void temp() + { + //^gordian + if (collidingWithFloor() && (getRenderState() != INVISIBLE)) + { + playSound(HIT_FLOOR); + } + //^gordian + } +} + +namespace Monolithic +{ + //^1 + class Bjorn + { + public: + Bjorn() + : velocity_(0), + x_(0), y_(0) + {} + + void update(World& world, Graphics& graphics); + + private: + static const int WALK_ACCELERATION = 1; + + int velocity_; + int x_, y_; + + Volume volume_; + + Sprite spriteStand_; + Sprite spriteWalkLeft_; + Sprite spriteWalkRight_; + }; + //^1 + + //^monolithic-update + void Bjorn::update(World& world, Graphics& graphics) + { + // Apply user input to hero's velocity. + switch (Controller::getJoystickDirection()) + { + case DIR_LEFT: + velocity_ -= WALK_ACCELERATION; + break; + + case DIR_RIGHT: + velocity_ += WALK_ACCELERATION; + break; + //^omit + case DIR_NONE: break; // Do nothing. + //^omit + } + + // Modify position by velocity. + x_ += velocity_; + world.resolveCollision(volume_, x_, y_, velocity_); + + // Draw the appropriate sprite. + Sprite* sprite = &spriteStand_; + if (velocity_ < 0) + { + sprite = &spriteWalkLeft_; + } + else if (velocity_ > 0) + { + sprite = &spriteWalkRight_; + } + + graphics.draw(*sprite, x_, y_); + } + //^monolithic-update +} + +namespace SplitInputComponent +{ + class Bjorn + { + public: + int velocity; + int x, y; + }; + + //^2 + class InputComponent + { + public: + void update(Bjorn& bjorn) + { + switch (Controller::getJoystickDirection()) + { + case DIR_LEFT: + bjorn.velocity -= WALK_ACCELERATION; + break; + + case DIR_RIGHT: + bjorn.velocity += WALK_ACCELERATION; + break; + //^omit + case DIR_NONE: break; // Do nothing. + //^omit + } + } + + private: + static const int WALK_ACCELERATION = 1; + }; + //^2 +} + +namespace SplitAIComponent +{ + class Bjorn; + + class InputComponent + { + public: + void update(Bjorn& bjorn); + }; + + //^3 + class Bjorn + { + public: + int velocity; + int x, y; + + void update(World& world, Graphics& graphics) + { + //^4 + input_.update(*this); + //^4 + + // Modify position by velocity. + x += velocity; + world.resolveCollision(volume_, x, y, velocity); + + // Draw the appropriate sprite. + Sprite* sprite = &spriteStand_; + if (velocity < 0) + { + sprite = &spriteWalkLeft_; + } + else if (velocity > 0) + { + sprite = &spriteWalkRight_; + } + + graphics.draw(*sprite, x, y); + } + + private: + InputComponent input_; + + Volume volume_; + + Sprite spriteStand_; + Sprite spriteWalkLeft_; + Sprite spriteWalkRight_; + }; + //^3 +} + +namespace Components +{ + class Bjorn + { + public: + int velocity; + int x, y; + }; + + class InputComponent + { + public: + void update(Bjorn& bjorn) {} + }; + + //^5 + class PhysicsComponent + { + public: + void update(Bjorn& bjorn, World& world) + { + bjorn.x += bjorn.velocity; + world.resolveCollision(volume_, + bjorn.x, bjorn.y, bjorn.velocity); + } + + private: + Volume volume_; + }; + //^5 + + //^6 + class GraphicsComponent + { + public: + void update(Bjorn& bjorn, Graphics& graphics) + { + Sprite* sprite = &spriteStand_; + if (bjorn.velocity < 0) + { + sprite = &spriteWalkLeft_; + } + else if (bjorn.velocity > 0) + { + sprite = &spriteWalkRight_; + } + + graphics.draw(*sprite, bjorn.x, bjorn.y); + } + + private: + Sprite spriteStand_; + Sprite spriteWalkLeft_; + Sprite spriteWalkRight_; + }; + //^6 +} + +namespace ComponentBjorn +{ + class Bjorn; + + class InputComponent + { + public: + void update(Bjorn& bjorn) {} + }; + + class PhysicsComponent + { + public: + void update(Bjorn& bjorn, World& world) {} + }; + + class GraphicsComponent + { + public: + void update(Bjorn& bjorn, Graphics& graphics) {} + }; + + //^7 + class Bjorn + { + public: + int velocity; + int x, y; + + void update(World& world, Graphics& graphics) + { + input_.update(*this); + physics_.update(*this, world); + graphics_.update(*this, graphics); + } + + private: + InputComponent input_; + PhysicsComponent physics_; + GraphicsComponent graphics_; + }; + //^7 +} + +namespace AbstractInput +{ + class Bjorn + { + public: + int velocity; + int x, y; + }; + + //^8 + class InputComponent + { + public: + virtual ~InputComponent() {} + virtual void update(Bjorn& bjorn) = 0; + }; + //^8 + + //^9 + class PlayerInputComponent : public InputComponent + { + public: + virtual void update(Bjorn& bjorn) + { + switch (Controller::getJoystickDirection()) + { + case DIR_LEFT: + bjorn.velocity -= WALK_ACCELERATION; + break; + + case DIR_RIGHT: + bjorn.velocity += WALK_ACCELERATION; + break; + //^omit + case DIR_NONE: break; // Do nothing. + //^omit + } + } + + private: + static const int WALK_ACCELERATION = 1; + }; + //^9 +} + +namespace AbstractInputBjorn +{ + class Bjorn; + + class InputComponent + { + public: + void update(Bjorn& bjorn) + { + } + }; + + class PlayerInputComponent : public InputComponent {}; + + class PhysicsComponent + { + public: + void update(Bjorn& bjorn, World& world) + { + } + }; + + class GraphicsComponent + { + public: + void update(Bjorn& bjorn, Graphics& graphics) + { + } + }; + + //^10 + class Bjorn + { + public: + int velocity; + int x, y; + + Bjorn(InputComponent* input) + : input_(input) + {} + + void update(World& world, Graphics& graphics) + { + input_->update(*this); + physics_.update(*this, world); + graphics_.update(*this, graphics); + } + + private: + InputComponent* input_; + PhysicsComponent physics_; + GraphicsComponent graphics_; + }; + //^10 + + //^12 + class DemoInputComponent : public InputComponent + { + public: + virtual void update(Bjorn& bjorn) + { + // AI to automatically control Bjorn... + } + }; + //^12 + + void makeBjorn() + { + { + //^11 + Bjorn* bjorn = new Bjorn(new PlayerInputComponent()); + //^11 + use(bjorn); + } + + { + //^13 + Bjorn* bjorn = new Bjorn(new DemoInputComponent()); + //^13 + use(bjorn); + } + } +} + +namespace BaseGameObject +{ + class GameObject; + + class InputComponent + { + public: + virtual ~InputComponent() {} + virtual void update(GameObject& obj) = 0; + }; + + class PlayerInputComponent : public InputComponent + { + public: + virtual void update(GameObject& obj) + { + } + }; + + //^14 + class PhysicsComponent + { + public: + virtual ~PhysicsComponent() {} + virtual void update(GameObject& obj, World& world) = 0; + }; + + class GraphicsComponent + { + public: + virtual ~GraphicsComponent() {} + virtual void update(GameObject& obj, Graphics& graphics) = 0; + }; + //^14 + + //^15 + class GameObject + { + public: + int velocity; + int x, y; + + GameObject(InputComponent* input, + PhysicsComponent* physics, + GraphicsComponent* graphics) + : input_(input), + physics_(physics), + graphics_(graphics) + {} + + void update(World& world, Graphics& graphics) + { + input_->update(*this); + physics_->update(*this, world); + graphics_->update(*this, graphics); + } + + private: + InputComponent* input_; + PhysicsComponent* physics_; + GraphicsComponent* graphics_; + }; + //^15 + + //^16 + class BjornPhysicsComponent : public PhysicsComponent + { + public: + virtual void update(GameObject& obj, World& world) + { + // Physics code... + } + }; + + class BjornGraphicsComponent : public GraphicsComponent + { + public: + virtual void update(GameObject& obj, Graphics& graphics) + { + // Graphics code... + } + }; + //^16 + + //^17 + GameObject* createBjorn() + { + return new GameObject(new PlayerInputComponent(), + new BjornPhysicsComponent(), + new BjornGraphicsComponent()); + } + //^17 +} + +namespace DirectComponentRef +{ + class BjornPhysicsComponent + { + public: + bool isOnGround() { return true; } + }; + + class GameObject + { + public: + int velocity; + int x, y; + }; + + //^18 + class BjornGraphicsComponent + { + public: + BjornGraphicsComponent(BjornPhysicsComponent* physics) + : physics_(physics) + {} + + void Update(GameObject& obj, Graphics& graphics) + { + Sprite* sprite; + if (!physics_->isOnGround()) + { + sprite = &spriteJump_; + } + else + { + // Existing graphics code... + //^omit + sprite = NULL; + //^omit + } + + graphics.draw(*sprite, obj.x, obj.y); + } + + private: + BjornPhysicsComponent* physics_; + + Sprite spriteStand_; + Sprite spriteWalkLeft_; + Sprite spriteWalkRight_; + Sprite spriteJump_; + }; + //^18 +} + +namespace ComponentMessaging +{ + //^19 + class Component + { + public: + virtual ~Component() {} + virtual void receive(int message) = 0; + }; + //^19 + + //^20 + class ContainerObject + { + public: + void send(int message) + { + for (int i = 0; i < MAX_COMPONENTS; i++) + { + if (components_[i] != NULL) + { + components_[i]->receive(message); + } + } + } + + private: + static const int MAX_COMPONENTS = 10; + Component* components_[MAX_COMPONENTS]; + }; +} diff --git a/code/cpp/cpp.xcodeproj/project.pbxproj b/code/cpp/cpp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..736dda6 --- /dev/null +++ b/code/cpp/cpp.xcodeproj/project.pbxproj @@ -0,0 +1,269 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 2924A07D16E63F5400515661 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2924A07C16E63F5400515661 /* main.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 29F2A2A416E63EBB005803DA /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 290D349B17B17F4500FF653B /* update-method.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "update-method.h"; sourceTree = SOURCE_ROOT; }; + 2924A07416E63F1D00515661 /* component.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = component.h; sourceTree = SOURCE_ROOT; }; + 2924A07516E63F1D00515661 /* double-buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "double-buffer.h"; sourceTree = SOURCE_ROOT; }; + 2924A07616E63F1D00515661 /* hello-world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "hello-world.h"; sourceTree = SOURCE_ROOT; }; + 2924A07716E63F1D00515661 /* object-pool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "object-pool.h"; sourceTree = SOURCE_ROOT; }; + 2924A07816E63F1D00515661 /* service-locator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "service-locator.h"; sourceTree = SOURCE_ROOT; }; + 2924A07916E63F1D00515661 /* singleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = singleton.h; sourceTree = SOURCE_ROOT; }; + 2924A07A16E63F1D00515661 /* subclass-sandbox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "subclass-sandbox.h"; sourceTree = SOURCE_ROOT; }; + 2924A07B16E63F1D00515661 /* type-object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "type-object.h"; sourceTree = SOURCE_ROOT; }; + 2924A07C16E63F5400515661 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = SOURCE_ROOT; }; + 2924A07E16E63FB300515661 /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = SOURCE_ROOT; }; + 292AAF741888E6BF0059E300 /* event-queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "event-queue.h"; sourceTree = SOURCE_ROOT; }; + 292FD0FD180392A6003CE36E /* command.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = command.h; sourceTree = SOURCE_ROOT; }; + 29360F5A177949FD0005D95F /* spatial-partition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "spatial-partition.h"; sourceTree = SOURCE_ROOT; }; + 29425F4B18BDB55000F1C528 /* bytecode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bytecode.h; sourceTree = SOURCE_ROOT; }; + 2947E82C17C32E3500257E9B /* introduction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = introduction.h; sourceTree = SOURCE_ROOT; }; + 294AEA8E16E792ED004E3BD0 /* game-loop.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "game-loop.h"; sourceTree = SOURCE_ROOT; }; + 295A7BB9179246E100A8C792 /* state.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = state.h; sourceTree = SOURCE_ROOT; }; + 298A366118134C080009A9D6 /* observer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = observer.h; sourceTree = SOURCE_ROOT; }; + 299FBF6F17D988660018EFD3 /* dirty-flag.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "dirty-flag.h"; sourceTree = SOURCE_ROOT; }; + 29AB1F331817291A004B501E /* prototype.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = prototype.h; sourceTree = SOURCE_ROOT; }; + 29B14848183F37820006AABE /* data-locality.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "data-locality.h"; sourceTree = SOURCE_ROOT; }; + 29BC60121779DA6C00C5D616 /* expect.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = expect.h; sourceTree = SOURCE_ROOT; }; + 29F2A2A616E63EBB005803DA /* cpp */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = cpp; sourceTree = BUILT_PRODUCTS_DIR; }; + 29FA84A017EDEC6D00D5519E /* flyweight.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = flyweight.h; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 29F2A2A316E63EBB005803DA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29F2A29D16E63EBB005803DA = { + isa = PBXGroup; + children = ( + 29F2A2A816E63EBB005803DA /* cpp */, + 29F2A2A716E63EBB005803DA /* Products */, + ); + sourceTree = ""; + }; + 29F2A2A716E63EBB005803DA /* Products */ = { + isa = PBXGroup; + children = ( + 29F2A2A616E63EBB005803DA /* cpp */, + ); + name = Products; + sourceTree = ""; + }; + 29F2A2A816E63EBB005803DA /* cpp */ = { + isa = PBXGroup; + children = ( + 2924A07C16E63F5400515661 /* main.cpp */, + 29360F5A177949FD0005D95F /* spatial-partition.h */, + 2924A07E16E63FB300515661 /* common.h */, + 2924A07416E63F1D00515661 /* component.h */, + 2924A07516E63F1D00515661 /* double-buffer.h */, + 2924A07616E63F1D00515661 /* hello-world.h */, + 2924A07716E63F1D00515661 /* object-pool.h */, + 2924A07816E63F1D00515661 /* service-locator.h */, + 2924A07916E63F1D00515661 /* singleton.h */, + 2924A07A16E63F1D00515661 /* subclass-sandbox.h */, + 2924A07B16E63F1D00515661 /* type-object.h */, + 294AEA8E16E792ED004E3BD0 /* game-loop.h */, + 29BC60121779DA6C00C5D616 /* expect.h */, + 295A7BB9179246E100A8C792 /* state.h */, + 290D349B17B17F4500FF653B /* update-method.h */, + 2947E82C17C32E3500257E9B /* introduction.h */, + 299FBF6F17D988660018EFD3 /* dirty-flag.h */, + 29FA84A017EDEC6D00D5519E /* flyweight.h */, + 292FD0FD180392A6003CE36E /* command.h */, + 29AB1F331817291A004B501E /* prototype.h */, + 29B14848183F37820006AABE /* data-locality.h */, + 298A366118134C080009A9D6 /* observer.h */, + 292AAF741888E6BF0059E300 /* event-queue.h */, + 29425F4B18BDB55000F1C528 /* bytecode.h */, + ); + name = cpp; + path = gpp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 29F2A2A516E63EBB005803DA /* cpp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 29F2A2AF16E63EBB005803DA /* Build configuration list for PBXNativeTarget "cpp" */; + buildPhases = ( + 29F2A2A216E63EBB005803DA /* Sources */, + 29F2A2A316E63EBB005803DA /* Frameworks */, + 29F2A2A416E63EBB005803DA /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = cpp; + productName = gpp; + productReference = 29F2A2A616E63EBB005803DA /* cpp */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29F2A29E16E63EBB005803DA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Bob Nystrom"; + }; + buildConfigurationList = 29F2A2A116E63EBB005803DA /* Build configuration list for PBXProject "cpp" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 29F2A29D16E63EBB005803DA; + productRefGroup = 29F2A2A716E63EBB005803DA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 29F2A2A516E63EBB005803DA /* cpp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 29F2A2A216E63EBB005803DA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2924A07D16E63F5400515661 /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 29F2A2AD16E63EBB005803DA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++98"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_PEDANTIC = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 29F2A2AE16E63EBB005803DA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "c++98"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_PEDANTIC = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 29F2A2B016E63EBB005803DA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++98"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 29F2A2B116E63EBB005803DA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++98"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 29F2A2A116E63EBB005803DA /* Build configuration list for PBXProject "cpp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29F2A2AD16E63EBB005803DA /* Debug */, + 29F2A2AE16E63EBB005803DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 29F2A2AF16E63EBB005803DA /* Build configuration list for PBXNativeTarget "cpp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29F2A2B016E63EBB005803DA /* Debug */, + 29F2A2B116E63EBB005803DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29F2A29E16E63EBB005803DA /* Project object */; +} diff --git a/code/cpp/data-locality.h b/code/cpp/data-locality.h new file mode 100644 index 0000000..d7c8936 --- /dev/null +++ b/code/cpp/data-locality.h @@ -0,0 +1,341 @@ +#include + +#ifndef cpp_data_locality_h +#define cpp_data_locality_h + +// TODO(bob): +// +// cache effects are magnified by: +// - turning up number of actors +// - adding padding to the actor class +// (both because it spaces the actors out more in memory) +// in examples below, creating much bigger shuffled array just to spread +// them out more. +// - adding padding to component magnifies it too, but also punishes best case + +namespace DataLocality +{ + void sleepFor500Cycles() {} + + struct Thing + { + void doStuff() {} + }; + + static const int NUM_THINGS = 3; + + void callDoNothing() + { + Thing things[NUM_THINGS]; + + //^do-nothing + for (int i = 0; i < NUM_THINGS; i++) + { + sleepFor500Cycles(); + things[i].doStuff(); + } + //^do-nothing + } + + //^components + class AIComponent + { + public: + void update() { /* Work with and modify state... */ } + + private: + // Goals, mood, etc. ... + }; + + class PhysicsComponent + { + public: + void update() { /* Work with and modify state... */ } + + private: + // Rigid body, velocity, mass, etc. ... + }; + + class RenderComponent + { + public: + void render() { /* Work with and modify state... */ } + + private: + // Mesh, textures, shaders, etc. ... + }; + //^components + + //^game-entity + class GameEntity + { + public: + GameEntity(AIComponent* ai, + PhysicsComponent* physics, + RenderComponent* render) + : ai_(ai), physics_(physics), render_(render) + {} + + AIComponent* ai() { return ai_; } + PhysicsComponent* physics() { return physics_; } + RenderComponent* render() { return render_; } + + private: + AIComponent* ai_; + PhysicsComponent* physics_; + RenderComponent* render_; + }; + //^game-entity + + void gameLoop() + { + int numEntities = 123; + GameEntity* entities[123]; + bool gameOver = false; + + //^game-loop + while (!gameOver) + { + // Process AI. + for (int i = 0; i < numEntities; i++) + { + entities[i]->ai()->update(); + } + + // Update physics. + for (int i = 0; i < numEntities; i++) + { + entities[i]->physics()->update(); + } + + // Draw to screen. + for (int i = 0; i < numEntities; i++) + { + entities[i]->render()->render(); + } + + // Other game loop machinery for timing... + } + //^game-loop + } + + static const int MAX_ENTITIES = 100; + + void componentArrays() + { + //^component-arrays + AIComponent* aiComponents = + new AIComponent[MAX_ENTITIES]; + PhysicsComponent* physicsComponents = + new PhysicsComponent[MAX_ENTITIES]; + RenderComponent* renderComponents = + new RenderComponent[MAX_ENTITIES]; + //^component-arrays + + int numEntities = 123; + bool gameOver = false; + + //^game-loop-arrays + while (!gameOver) + { + // Process AI. + for (int i = 0; i < numEntities; i++) + { + aiComponents[i].update(); + } + + // Update physics. + for (int i = 0; i < numEntities; i++) + { + physicsComponents[i].update(); + } + + // Draw to screen. + for (int i = 0; i < numEntities; i++) + { + renderComponents[i].render(); + } + + // Other game loop machinery for timing... + } + //^game-loop-arrays + + delete [] aiComponents; + delete [] physicsComponents; + delete [] renderComponents; + } + + //^particle-system + class Particle + { + public: + //^omit particle-system + bool isActive() { return false; } + //^omit particle-system + void update() { /* Gravity, etc. ... */ } + // Position, velocity, etc. ... + }; + + class ParticleSystem + { + public: + ParticleSystem() + : numParticles_(0) + {} + + void update(); + //^omit particle-system + void activateParticle(int index); + void deactivateParticle(int index); + //^omit particle-system + private: + static const int MAX_PARTICLES = 100000; + + int numParticles_; + Particle particles_[MAX_PARTICLES]; + }; + //^particle-system + + //^update-particle-system + void ParticleSystem::update() + { + for (int i = 0; i < numParticles_; i++) + { + particles_[i].update(); + } + } + //^update-particle-system + + void updateParticlesSlow() + { + Particle particles_[100]; + int numParticles_ = 0; + //^particles-is-active + for (int i = 0; i < numParticles_; i++) + { + if (particles_[i].isActive()) + { + particles_[i].update(); + } + } + //^particles-is-active + } + + Particle particles[100]; + int numActive_ = 0; + void updateParticles() + { + //^update-particles + for (int i = 0; i < numActive_; i++) + { + particles[i].update(); + } + //^update-particles + } + + //^activate-particle + void ParticleSystem::activateParticle(int index) + { + // Shouldn't already be active! + assert(index >= numActive_); + + // Swap it with the first inactive particle + // right after the active ones. + Particle temp = particles_[numActive_]; + particles_[numActive_] = particles_[index]; + particles_[index] = temp; + + // Now there's one more. + numActive_++; + } + //^activate-particle + + //^deactivate-particle + void ParticleSystem::deactivateParticle(int index) + { + // Shouldn't already be inactive! + assert(index < numActive_); + + // There's one fewer. + numActive_--; + + // Swap it with the last active particle + // right before the inactive ones. + Particle temp = particles_[numActive_]; + particles_[numActive_] = particles_[index]; + particles_[index] = temp; + } + //^deactivate-particle + + enum Mood { + MOOD_WISTFUL + }; + + class Animation {}; + class Vector {}; + class LootType {}; + + namespace HotColdMixed + { + //^ai-component + class AIComponent + { + public: + void update() { /* ... */ } + + private: + Animation* animation_; + double energy_; + Vector goalPos_; + }; + //^ai-component + } + + namespace HotColdMixedLoot + { + //^loot-drop + class AIComponent + { + public: + void update() { /* ... */ } + + private: + // Previous fields... + LootType drop_; + int minDrops_; + int maxDrops_; + double chanceOfDrop_; + }; + //^loot-drop + } + + namespace HotCold + { + class LootDrop; + + //^hot-cold + class AIComponent + { + public: + // Methods... + private: + Animation* animation_; + double energy_; + Vector goalPos_; + + LootDrop* loot_; + }; + + class LootDrop + { + friend class AIComponent; + LootType drop_; + int minDrops_; + int maxDrops_; + double chanceOfDrop_; + }; + //^hot-cold + } +} + +#endif diff --git a/code/cpp/dirty-flag.h b/code/cpp/dirty-flag.h new file mode 100644 index 0000000..31df713 --- /dev/null +++ b/code/cpp/dirty-flag.h @@ -0,0 +1,183 @@ +// +// dirty-flag.h +// cpp +// +// Created by Bob Nystrom on 9/5/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#ifndef cpp_dirty_flag_h +#define cpp_dirty_flag_h + +namespace DirtyFlag +{ + //^transform + class Transform + { + public: + static Transform origin(); + + Transform combine(Transform& other); + }; + //^transform + + Transform Transform::origin() + { + return Transform(); + } + + Transform Transform::combine(Transform& other) + { + return other; + } + + class Mesh; + static const int MAX_CHILDREN = 16; + + namespace Basic + { + //^graph-node + class GraphNode + { + public: + GraphNode(Mesh* mesh) + : mesh_(mesh), + local_(Transform::origin()) + {} + + private: + Transform local_; + Mesh* mesh_; + + GraphNode* children_[MAX_CHILDREN]; + int numChildren_; + }; + //^graph-node + + void root() + { + //^scene-graph + GraphNode* graph_ = new GraphNode(NULL); + // Add children to root graph node... + //^scene-graph + use(graph_); + } + } + + //^render + void renderMesh(Mesh* mesh, Transform transform); + //^render + + void renderMesh(Mesh* mesh, Transform transform) + { + + } + + namespace RenderOnTheFly + { + static const int MAX_CHILDREN = 16; + + class GraphNode + { + public: + GraphNode(Mesh* mesh) + : mesh_(mesh), + local_(Transform::origin()) + {} + + void render(Transform parentWorld); + + private: + Transform local_; + Mesh* mesh_; + + GraphNode* children_[MAX_CHILDREN]; + int numChildren_; + }; + + //^render-on-fly + void GraphNode::render(Transform parentWorld) + { + Transform world = local_.combine(parentWorld); + + if (mesh_) renderMesh(mesh_, world); + + for (int i = 0; i < numChildren_; i++) + { + children_[i]->render(world); + } + } + //^render-on-fly + + void root() + { + GraphNode* graph_ = new GraphNode(NULL); + //^render-root + graph_->render(Transform::origin()); + //^render-root + } + } + + namespace Dirty + { + //^dirty-graph-node + class GraphNode + { + public: + GraphNode(Mesh* mesh) + : mesh_(mesh), + local_(Transform::origin()), + dirty_(true) + {} + //^omit + void setTransform(Transform local); + void render(Transform parentWorld, bool dirty); + //^omit + + // Other methods... + + private: + Transform world_; + bool dirty_; + // Other fields... + //^omit + Transform local_; + Mesh* mesh_; + + static const int MAX_CHILDREN = 16; + GraphNode* children_[MAX_CHILDREN]; + int numChildren_; + //^omit + }; + //^dirty-graph-node + + //^set-transform + void GraphNode::setTransform(Transform local) + { + local_ = local; + dirty_ = true; + } + //^set-transform + + //^dirty-render + void GraphNode::render(Transform parentWorld, bool dirty) + { + dirty |= dirty_; + if (dirty) + { + world_ = local_.combine(parentWorld); + dirty_ = false; + } + + if (mesh_) renderMesh(mesh_, world_); + + for (int i = 0; i < numChildren_; i++) + { + children_[i]->render(world_, dirty); + } + } + //^dirty-render + } +} + +#endif diff --git a/code/cpp/double-buffer.h b/code/cpp/double-buffer.h new file mode 100644 index 0000000..d232d4b --- /dev/null +++ b/code/cpp/double-buffer.h @@ -0,0 +1,378 @@ +#include + +#define WHITE 0 +#define BLACK 1 + +//^1 +class Framebuffer +{ +public: + Framebuffer() { clear(); } + + void clear() + { + for (int i = 0; i < WIDTH * HEIGHT; i++) + { + pixels_[i] = WHITE; + } + } + + void draw(int x, int y) + { + pixels_[(WIDTH * y) + x] = BLACK; + } + + const char* getPixels() + { + return pixels_; + } + +private: + static const int WIDTH = 160; + static const int HEIGHT = 120; + + char pixels_[WIDTH * HEIGHT]; +}; +//^1 + +namespace Unbuffered +{ + //^2 + class Scene + { + public: + void draw() + { + buffer_.clear(); + + buffer_.draw(1, 1); + buffer_.draw(4, 1); + buffer_.draw(1, 3); + buffer_.draw(2, 4); + buffer_.draw(3, 4); + buffer_.draw(4, 3); + } + + Framebuffer& getBuffer() { return buffer_; } + + private: + Framebuffer buffer_; + }; + //^2 + + void InterruptMiddleOfDraw() + { + Framebuffer buffer_; + + //^3 + buffer_.draw(1, 1); + buffer_.draw(4, 1); + // <- Video driver reads pixels here! + buffer_.draw(1, 3); + buffer_.draw(2, 4); + buffer_.draw(3, 4); + buffer_.draw(4, 3); + //^3 + } +} + +namespace Buffered +{ + //^4 + class Scene + { + public: + Scene() + : current_(&buffers_[0]), + next_(&buffers_[1]) + {} + + void draw() + { + next_->clear(); + + next_->draw(1, 1); + // ... + next_->draw(4, 3); + + swap(); + } + + Framebuffer& getBuffer() { return *current_; } + + private: + void swap() + { + // Just switch the pointers. + Framebuffer* temp = current_; + current_ = next_; + next_ = temp; + } + + Framebuffer buffers_[2]; + Framebuffer* current_; + Framebuffer* next_; + }; + //^4 +} + +namespace UnbufferedSlapstick +{ + class Stage; + + //^5 + class Actor + { + public: + Actor() : slapped_(false) {} + + virtual ~Actor() {} + virtual void update() = 0; + + void reset() { slapped_ = false; } + void slap() { slapped_ = true; } + bool wasSlapped() { return slapped_; } + + private: + bool slapped_; + }; + //^5 + + //^6 + class Stage + { + public: + void add(Actor* actor, int index) + { + actors_[index] = actor; + } + + void update() + { + for (int i = 0; i < NUM_ACTORS; i++) + { + actors_[i]->update(); + actors_[i]->reset(); + } + } + + private: + static const int NUM_ACTORS = 3; + + Actor* actors_[NUM_ACTORS]; + }; + //^6 + + //^7 + class Comedian : public Actor + { + public: + //^omit + Comedian() : name_("") {} + Comedian(const char* name) : name_(name) {} + //^omit + void face(Actor* actor) { facing_ = actor; } + + virtual void update() + { + //^omit + if (wasSlapped()) std::cout << name_ << " was slapped" << std::endl; + //^omit + if (wasSlapped()) facing_->slap(); + } + + private: + //^omit + const char* name_; + //^omit + Actor* facing_; + }; + //^7 + + void sample1() + { + //^8 + Stage stage; + + Comedian* harry = new Comedian(); + Comedian* baldy = new Comedian(); + Comedian* chump = new Comedian(); + + harry->face(baldy); + baldy->face(chump); + chump->face(harry); + + stage.add(harry, 0); + stage.add(baldy, 1); + stage.add(chump, 2); + //^8 + + //^9 + harry->slap(); + + stage.update(); + //^9 + + //^10 + stage.add(harry, 2); + stage.add(baldy, 1); + stage.add(chump, 0); + //^10 + } + + void testComedy1(int a, int b, int c) + { + std::cout << std::endl << "test" << std::endl; + + Stage stage; + + Comedian* larry = new Comedian("larry"); + Comedian* curly = new Comedian("curly"); + Comedian* shemp = new Comedian("shemp"); + + larry->face(curly); + curly->face(shemp); + shemp->face(larry); + + stage.add(larry, a); + stage.add(curly, b); + stage.add(shemp, c); + + larry->slap(); + for (int i = 0; i < 3; i++) + { + std::cout << "update" << std::endl; + stage.update(); + } + } + + void testComedy() + { + testComedy1(0, 1, 2); + testComedy1(2, 1, 0); + } +} + +namespace BufferedSlapstick +{ + class Stage; + + //^11 + class Actor + { + public: + Actor() : currentSlapped_(false) {} + + virtual ~Actor() {} + virtual void update() = 0; + + void swap() + { + // Swap the buffer. + currentSlapped_ = nextSlapped_; + + // Clear the new "next" buffer. + nextSlapped_ = false; + } + + void slap() { nextSlapped_ = true; } + bool wasSlapped() { return currentSlapped_; } + + private: + bool currentSlapped_; + bool nextSlapped_; + }; + //^11 + + class Stage + { + public: + void add(Actor* actor, int index) { actors_[index] = actor; } + void update(); + private: + static const int NUM_ACTORS = 3; + + Actor* actors_[NUM_ACTORS]; + }; + + //^12 + void Stage::update() + { + for (int i = 0; i < NUM_ACTORS; i++) + { + actors_[i]->update(); + } + + for (int i = 0; i < NUM_ACTORS; i++) + { + actors_[i]->swap(); + } + } + //^12 + + class Comedian : public Actor + { + public: + //^omit + Comedian() : name_("") {} + Comedian(const char* name) : name_(name) {} + //^omit + void face(Actor* actor) { facing_ = actor; } + + virtual void update() + { + //^omit + if (wasSlapped()) std::cout << name_ << " was slapped" << std::endl; + //^omit + if (wasSlapped()) facing_->slap(); + } + + private: + //^omit + const char* name_; + //^omit + Actor* facing_; + }; + + void sample1() + { + Stage stage; + + Comedian* harry = new Comedian(); + Comedian* baldy = new Comedian(); + Comedian* chump = new Comedian(); + + harry->face(baldy); + baldy->face(chump); + chump->face(harry); + + stage.add(harry, 0); + stage.add(baldy, 1); + stage.add(chump, 2); + + harry->slap(); + + stage.update(); + } +} + +namespace SwapOffset +{ + //^13 + class Actor + { + public: + static void init() { current_ = 0; } + static void swap() { current_ = next(); } + + void slap() { slapped_[next()] = true; } + bool wasSlapped() { return slapped_[current_]; } + + private: + static int current_; + static int next() { return 1 - current_; } + + bool slapped_[2]; + }; + //^13 +} diff --git a/code/cpp/event-queue.h b/code/cpp/event-queue.h new file mode 100644 index 0000000..27553c5 --- /dev/null +++ b/code/cpp/event-queue.h @@ -0,0 +1,378 @@ +#ifndef cpp_event_queue_h +#define cpp_event_queue_h + +namespace EventQueue +{ + typedef int ResourceId; + typedef int SoundId; + + static const int SOUND_BLOOP = 1; + static const int VOL_MAX = 1; + + ResourceId loadSound(SoundId id) { return 0; } + int findOpenChannel() { return -1; } + void startSound(ResourceId resource, int channel, int volume) {} + + namespace EventLoop + { + typedef int Event; + Event getNextEvent() { return 0; } + + void eventLoop() + { + bool running = true; + //^event-loop + while (running) + { + Event event = getNextEvent(); + // Handle event... + //^omit + use(event); + //^omit + } + //^event-loop + } + } + + namespace Unqueued + { + //^sync-api + class Audio + { + public: + static void playSound(SoundId id, int volume); + }; + //^sync-api + + //^sync-impl + void Audio::playSound(SoundId id, int volume) + { + ResourceId resource = loadSound(id); + int channel = findOpenChannel(); + if (channel == -1) return; + startSound(resource, channel, volume); + } + //^sync-impl + + //^menu-bloop + class Menu + { + public: + void onSelect(int index) + { + Audio::playSound(SOUND_BLOOP, VOL_MAX); + // Other stuff... + } + }; + //^menu-bloop + } + + //^play-message + struct PlayMessage + { + SoundId id; + int volume; + }; + //^play-message + + namespace DeferList + { + //^pending-array + class Audio + { + public: + static void init() + { + numPending_ = 0; + } + + // Other stuff... + //^omit + static void playSound(SoundId id, int volume); + //^omit + private: + static const int MAX_PENDING = 16; + + static PlayMessage pending_[MAX_PENDING]; + static int numPending_; + }; + //^pending-array + + //^array-play + void Audio::playSound(SoundId id, int volume) + { + assert(numPending_ < MAX_PENDING); + + pending_[numPending_].id = id; + pending_[numPending_].volume = volume; + numPending_++; + } + //^array-play + + PlayMessage Audio::pending_[MAX_PENDING]; + int Audio::numPending_; + } + + namespace DeferList2 + { + //^array-update + class Audio + { + public: + static void update() + { + for (int i = 0; i < numPending_; i++) + { + ResourceId resource = loadSound(pending_[i].id); + int channel = findOpenChannel(); + if (channel == -1) return; + startSound(resource, channel, pending_[i].volume); + } + + numPending_ = 0; + } + + // Other stuff... + //^omit + private: + static const int MAX_PENDING = 16; + + static PlayMessage pending_[MAX_PENDING]; + static int numPending_; + //^omit + }; + //^array-update + + PlayMessage Audio::pending_[MAX_PENDING]; + int Audio::numPending_; + } + + namespace HeadTail + { + //^head-tail + class Audio + { + public: + static void init() + { + head_ = 0; + tail_ = 0; + } + + // Methods... + //^omit + static void playSound(SoundId id, int volume); + static void update(); + //^omit + private: + //^omit + static const int MAX_PENDING = 16; + + static PlayMessage pending_[MAX_PENDING]; + //^omit + static int head_; + static int tail_; + + // Array... + }; + //^head-tail + + //^tail-play + void Audio::playSound(SoundId id, int volume) + { + assert(tail_ < MAX_PENDING); + + // Add to the end of the list. + pending_[tail_].id = id; + pending_[tail_].volume = volume; + tail_++; + } + //^tail-play + + //^tail-update + void Audio::update() + { + // If there are no pending requests, do nothing. + if (head_ == tail_) return; + + ResourceId resource = loadSound(pending_[head_].id); + int channel = findOpenChannel(); + if (channel == -1) return; + startSound(resource, channel, pending_[head_].volume); + + head_++; + } + //^tail-update + + PlayMessage Audio::pending_[MAX_PENDING]; + int Audio::tail_; + int Audio::head_; + } + + namespace Ring + { + class Audio + { + public: + static void init() + { + head_ = 0; + tail_ = 0; + } + + static void playSound(SoundId id, int volume); + static void update(); + private: + static const int MAX_PENDING = 16; + + static PlayMessage pending_[MAX_PENDING]; + static int head_; + static int tail_; + }; + + //^ring-play + void Audio::playSound(SoundId id, int volume) + { + assert((tail_ + 1) % MAX_PENDING != head_); + + // Add to the end of the list. + pending_[tail_].id = id; + pending_[tail_].volume = volume; + tail_ = (tail_ + 1) % MAX_PENDING; + } + //^ring-play + + //^ring-update + void Audio::update() + { + // If there are no pending requests, do nothing. + if (head_ == tail_) return; + + ResourceId resource = loadSound(pending_[head_].id); + int channel = findOpenChannel(); + if (channel == -1) return; + startSound(resource, channel, pending_[head_].volume); + + head_ = (head_ + 1) % MAX_PENDING; + } + //^ring-update + + PlayMessage Audio::pending_[MAX_PENDING]; + int Audio::tail_; + int Audio::head_; + } + + namespace Duplicate + { + class Audio + { + public: + static void init() + { + head_ = 0; + tail_ = 0; + } + + static void playSound(SoundId id, int volume); + static void update(); + private: + static const int MAX_PENDING = 16; + + static PlayMessage pending_[MAX_PENDING]; + static int head_; + static int tail_; + }; + + int max(int a, int b) { return a; } + + //^drop-dupe-play + void Audio::playSound(SoundId id, int volume) + { + // Walk the pending requests. + for (int i = head_; i != tail_; + i = (i + 1) % MAX_PENDING) + { + if (pending_[i].id == id) + { + // Use the larger of the two volumes. + pending_[i].volume = max(volume, pending_[i].volume); + + // Don't need to enqueue. + return; + } + } + + // Previous code... + } + //^drop-dupe-play + + //^ring-update + void Audio::update() + { + // If there are no pending requests, do nothing. + if (head_ == tail_) return; + + ResourceId resource = loadSound(pending_[head_].id); + int channel = findOpenChannel(); + if (channel == -1) return; + startSound(resource, channel, pending_[head_].volume); + + head_ = (head_ + 1) % MAX_PENDING; + } + //^ring-update + + PlayMessage Audio::pending_[MAX_PENDING]; + int Audio::tail_; + int Audio::head_; + } + + namespace Queued + { + // head (0) + // | tail (3) + // v v + // +-+-+-+-+-+-+-+-+-+ + // |A|B|C| | | | | | | + // +-+-+-+-+-+-+-+-+-+ + + class Audio + { + public: + Audio() + : head_(0), + numMessages_(0) + {} + + void playSound(SoundId id, int volume) + { + assert(numMessages_ < MAX_MESSAGES); + + queue_[head_].id = id; + queue_[head_].volume = volume; + + head_ = (head_ + 1) % MAX_MESSAGES; + numMessages_++; + } + + void update() + { + + } + + private: + class SoundMessage + { + public: + SoundId id; + int volume; + }; + + static const int MAX_MESSAGES = 16; + + SoundMessage queue_[MAX_MESSAGES]; + int head_; + int numMessages_; + }; + } +} +#endif diff --git a/code/cpp/expect.h b/code/cpp/expect.h new file mode 100644 index 0000000..c99dd25 --- /dev/null +++ b/code/cpp/expect.h @@ -0,0 +1,33 @@ +// +// expect.h +// gpp +// +// Created by Bob Nystrom on 6/25/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#ifndef gpp_expect_h +#define gpp_expect_h + +#define EXPECT(condition) \ +expect_(__FILE__, __LINE__, #condition, condition) + +static void expect_(const char * file, int line, + const char * expression, + bool condition) +{ + using std::cout; + using std::endl; + + if (condition) + { + cout << "PASS: " << expression << endl; + } + else + { + cout << "FAIL: " << expression << endl; + cout << " " << file << ":" << line << endl; + } +} + +#endif diff --git a/code/cpp/flyweight.h b/code/cpp/flyweight.h new file mode 100644 index 0000000..40c5b64 --- /dev/null +++ b/code/cpp/flyweight.h @@ -0,0 +1,264 @@ +// +// flyweight.h +// cpp +// +// Created by Bob Nystrom on 9/21/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#ifndef cpp_flyweight_h +#define cpp_flyweight_h + +namespace Flyweight +{ + class Mesh {}; + class Skeleton {}; + class Texture {}; + class Pose {}; + class Vector {}; + class Color {}; + + static const int WIDTH = 1024; + static const int HEIGHT = 1024; + + static Texture GRASS_TEXTURE; + static Texture HILL_TEXTURE; + static Texture RIVER_TEXTURE; + + int random(int max) { return 0; } + + namespace HeavyTree + { + //^heavy-tree + class Tree + { + private: + Mesh mesh_; + Texture bark_; + Texture leaves_; + Vector position_; + double height_; + double thickness_; + Color barkTint_; + Color leafTint_; + }; + //^heavy-tree + } + + namespace SplitTree + { + //^tree-model + class TreeModel + { + private: + Mesh mesh_; + Texture bark_; + Texture leaves_; + }; + //^tree-model + + //^split-tree + class Tree + { + private: + TreeModel* model_; + + Vector position_; + double height_; + double thickness_; + Color barkTint_; + Color leafTint_; + }; + //^split-tree + } + + namespace TerrainEnum + { + //^terrain-enum + enum Terrain + { + TERRAIN_GRASS, + TERRAIN_HILL, + TERRAIN_RIVER + // Other terrains... + }; + //^terrain-enum + + //^enum-world + class World + { + private: + Terrain tiles_[WIDTH][HEIGHT]; + //^omit + int getMovementCost(int x, int y); + bool isWater(int x, int y); + //^omit + }; + //^enum-world + + //^enum-data + int World::getMovementCost(int x, int y) + { + switch (tiles_[x][y]) + { + case TERRAIN_GRASS: return 1; + case TERRAIN_HILL: return 3; + case TERRAIN_RIVER: return 2; + // Other terrains... + } + } + + bool World::isWater(int x, int y) + { + switch (tiles_[x][y]) + { + case TERRAIN_GRASS: return false; + case TERRAIN_HILL: return false; + case TERRAIN_RIVER: return true; + // Other terrains... + } + } + //^enum-data + } + + namespace TerrainClass + { + //^terrain-class + class Terrain + { + public: + Terrain(int movementCost, + bool isWater, + Texture texture) + : movementCost_(movementCost), + isWater_(isWater), + texture_(texture) + {} + + int getMovementCost() const { return movementCost_; } + bool isWater() const { return isWater_; } + const Texture& getTexture() const { return texture_; } + + private: + int movementCost_; + bool isWater_; + Texture texture_; + }; + //^terrain-class + + //^world-terrain-pointers + class World + { + //^omit + public: + World() + : grassTerrain_(1, false, GRASS_TEXTURE), + hillTerrain_(3, false, HILL_TEXTURE), + riverTerrain_(2, true, RIVER_TEXTURE) + {} + const Terrain& getTile(int x, int y) const; + //^omit + private: + Terrain* tiles_[WIDTH][HEIGHT]; + + // Other stuff... + //^omit + Terrain grassTerrain_; + Terrain hillTerrain_; + Terrain riverTerrain_; + void generateTerrain(); + //^omit + }; + //^world-terrain-pointers + + //^generate + void World::generateTerrain() + { + // Fill the ground with grass. + for (int x = 0; x < WIDTH; x++) + { + for (int y = 0; y < HEIGHT; y++) + { + // Sprinkle some hills. + if (random(10) == 0) + { + tiles_[x][y] = &hillTerrain_; + } + else + { + tiles_[x][y] = &grassTerrain_; + } + } + } + + // Lay a river. + int x = random(WIDTH); + for (int y = 0; y < HEIGHT; y++) { + tiles_[x][y] = &riverTerrain_; + } + } + //^generate + + //^get-tile + const Terrain& World::getTile(int x, int y) const + { + return *tiles_[x][y]; + } + //^get-tile + + void foo() + { + World world; + + //^use-get-tile + int cost = world.getTile(2, 3).getMovementCost(); + //^use-get-tile + use(cost); + } + } + + namespace WorldTerrain + { + class Terrain + { + public: + Terrain(int movementCost, + bool isWater, + Texture texture) + : movementCost_(movementCost), + isWater_(isWater), + texture_(texture) + {} + + int getMovementCost() const { return movementCost_; } + bool isWater() const { return isWater_; } + const Texture& getTexture() const { return texture_; } + + private: + int movementCost_; + bool isWater_; + Texture texture_; + }; + + //^world-terrain + class World + { + public: + World() + : grassTerrain_(1, false, GRASS_TEXTURE), + hillTerrain_(3, false, HILL_TEXTURE), + riverTerrain_(2, true, RIVER_TEXTURE) + {} + + private: + Terrain grassTerrain_; + Terrain hillTerrain_; + Terrain riverTerrain_; + + // Other stuff... + }; + //^world-terrain + } +} + +#endif diff --git a/code/cpp/game-loop.h b/code/cpp/game-loop.h new file mode 100644 index 0000000..d0250a4 --- /dev/null +++ b/code/cpp/game-loop.h @@ -0,0 +1,144 @@ +void processInput() {} +void update() {} +void update(double elapsed) {} +void render() {} +void render(double elapsed) {} + +double getCurrentTime() { return 0; } +void sleep(double time) {} + +int FPS = 60; +int MS_PER_FRAME = 1000 / FPS; +int MS_PER_TICK = 1000 / FPS; + +namespace Repl +{ + char* readCommand() { return NULL; } + void handleCommand(char* command) {} + + void loop() + { + //^1 + while (true) + { + char* command = readCommand(); + handleCommand(command); + } + //^1 + } +} + +namespace EventLoop +{ + class Event {}; + Event* waitForEvent() { return NULL; } + bool dispatchEvent(Event* event) { return false; } + + void loop() + { + //^2 + while (true) + { + Event* event = waitForEvent(); + dispatchEvent(event); + } + //^2 + } +} + +namespace FastAsPossible +{ + void runGame() + { + //^3 + while (true) + { + processInput(); + update(); + render(); + } + //^3 + } +} + +namespace FixedFramerate +{ + void runGame() + { + //^4 + while (true) + { + double start = getCurrentTime(); + processInput(); + update(); + render(); + + sleep(start + MS_PER_FRAME - getCurrentTime()); + } + //^4 + } +} + + +namespace FluidFramerate +{ + void runGame() + { + //^5 + double lastTime = getCurrentTime(); + while (true) + { + double current = getCurrentTime(); + double elapsed = current - lastTime; + processInput(); + update(elapsed); + render(); + lastTime = current; + } + //^5 + } +} + +namespace FixedUpdateFramerate +{ + void runGame() + { + const double MS_PER_UPDATE = 8; + + //^6 + double previous = getCurrentTime(); + double lag = 0.0; + while (true) + { + double current = getCurrentTime(); + double elapsed = current - previous; + previous = current; + lag += elapsed; + + processInput(); + + while (lag >= MS_PER_UPDATE) + { + update(); + lag -= MS_PER_UPDATE; + } + + render(); + } + //^6 + } +} + + +namespace Interpolate +{ + void runGame() + { + const double MS_PER_UPDATE = 8; + double lag = 0; + + //^7 + render(lag / MS_PER_UPDATE); + //^7 + } +} diff --git a/code/cpp/hello-world.h b/code/cpp/hello-world.h new file mode 100644 index 0000000..a3f917f --- /dev/null +++ b/code/cpp/hello-world.h @@ -0,0 +1,15 @@ +#include + +class HelloWorld +{ +public: + //^1 + // 64 characters --------------------------------------------------------| + static void Do() + { + //^2 + std::cout << "Hello world." << std::endl; + //^2 + } + //^1 +}; \ No newline at end of file diff --git a/code/cpp/introduction.h b/code/cpp/introduction.h new file mode 100644 index 0000000..7ac34d5 --- /dev/null +++ b/code/cpp/introduction.h @@ -0,0 +1,20 @@ +// +// introduction.h +// cpp +// +// Created by Bob Nystrom on 8/19/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#ifndef cpp_introduction_h +#define cpp_introduction_h + +//^update +bool update() +{ + // Do work... + return isDone(); +} +//^update + +#endif diff --git a/code/cpp/main.cpp b/code/cpp/main.cpp new file mode 100644 index 0000000..65aa07e --- /dev/null +++ b/code/cpp/main.cpp @@ -0,0 +1,31 @@ +#include + +#include "hello-world.h" +#include "component.h" +#include "object-pool.h" +#include "service-locator.h" +#include "singleton.h" +#include "double-buffer.h" +#include "subclass-sandbox.h" +#include "type-object.h" +#include "game-loop.h" +#include "spatial-partition.h" +#include "state.h" +#include "update-method.h" +#include "dirty-flag.h" +#include "flyweight.h" +#include "command.h" +#include "observer.h" +#include "prototype.h" +#include "data-locality.h" +#include "event-queue.h" +#include "bytecode.h" + +int main (int argc, char * const argv[]) +{ + UnbufferedSlapstick::testComedy(); + SpatialPartition::test(); + ObserverPattern::test(); + + return 0; +} diff --git a/code/cpp/object-pool.h b/code/cpp/object-pool.h new file mode 100644 index 0000000..d47d278 --- /dev/null +++ b/code/cpp/object-pool.h @@ -0,0 +1,333 @@ +#include + +namespace Version1 +{ + class ParticlePool; + + //^1 + class Particle + { + public: + Particle() + : framesLeft_(0) + {} + + void init(double x, double y, + double xVel, double yVel, int lifetime) + { + x_ = x; y_ = y; + xVel_ = xVel; yVel_ = yVel; + framesLeft_ = lifetime; + } + + void animate() + { + if (!inUse()) return; + + framesLeft_--; + x_ += xVel_; + y_ += yVel_; + } + + bool inUse() const { return framesLeft_ > 0; } + + private: + int framesLeft_; + double x_, y_; + double xVel_, yVel_; + }; + //^1 + + //^2 + class ParticlePool + { + public: + void create(double x, double y, + double xVel, double yVel, int lifetime); + + void animate() + { + for (int i = 0; i < POOL_SIZE; i++) + { + particles_[i].animate(); + } + } + + private: + static const int POOL_SIZE = 100; + Particle particles_[POOL_SIZE]; + }; + //^2 + + //^3 + void ParticlePool::create(double x, double y, + double xVel, double yVel, + int lifetime) + { + // Find an available particle. + for (int i = 0; i < POOL_SIZE; i++) + { + if (!particles_[i].inUse()) + { + particles_[i].init(x, y, xVel, yVel, lifetime); + //^omit + + std::cout << "created " << i << std::endl; + //^omit + return; + } + } + } + //^3 +}; + +namespace Temp1 +{ + //^4 + class Particle + { + public: + //^omit + void init(double x, double y, + double xVel, double yVel, int lifetime) {} + bool animate(); + bool inUse() { return false; } + double x_; + double y_; + double xVel_; + double yVel_; + //^omit + // ... + + Particle* getNext() const { return state_.next; } + void setNext(Particle* next) { state_.next = next; } + + private: + int framesLeft_; + + union + { + // State when it's in use. + struct + { + double x, y; + double xVel, yVel; + } live; + + // State when it's available. + Particle* next; + } state_; + }; + //^4 + + //^particle-animate + bool Particle::animate() + { + if (!inUse()) return false; + + framesLeft_--; + x_ += xVel_; + y_ += yVel_; + + return framesLeft_ == 0; + } + //^particle-animate + + //^5 + class ParticlePool + { + //^omit + ParticlePool(); + void create(double x, double y, + double xVel, double yVel, int lifetime); + void animate(); + //^omit + // ... + private: + //^omit + static const int POOL_SIZE = 100; + Particle particles_[POOL_SIZE]; + //^omit + Particle* firstAvailable_; + }; + //^5 + + //^6 + ParticlePool::ParticlePool() + { + // The first one is available. + firstAvailable_ = &particles_[0]; + + // Each particle points to the next. + for (int i = 0; i < POOL_SIZE - 1; i++) + { + particles_[i].setNext(&particles_[i + 1]); + } + + // The last one terminates the list. + particles_[POOL_SIZE - 1].setNext(NULL); + } + //^6 + + //^7 + void ParticlePool::create(double x, double y, + double xVel, double yVel, + int lifetime) + { + // Make sure the pool isn't full. + assert(firstAvailable_ != NULL); + + // Remove it from the available list. + Particle* newParticle = firstAvailable_; + firstAvailable_ = newParticle->getNext(); + + newParticle->init(x, y, xVel, yVel, lifetime); + } + //^7 + + //^8 + void ParticlePool::animate() + { + for (int i = 0; i < POOL_SIZE; i++) + { + if (particles_[i].animate()) + { + // Add this particle to the front of the list. + particles_[i].setNext(firstAvailable_); + firstAvailable_ = &particles_[i]; + } + } + } + //^8 +}; + +namespace Temp2 +{ + class ParticlePool; + + //^10 + class Particle + { + friend class ParticlePool; + + private: + Particle() + : inUse_(false) + {} + + bool inUse_; + }; + + class ParticlePool + { + Particle pool_[100]; + }; + //^10 +} + +namespace Temp3 +{ + //^11 + template + class GenericPool + { + private: + static const int POOL_SIZE = 100; + + TObject pool_[POOL_SIZE]; + bool inUse_[POOL_SIZE]; + }; + //^11 +} + +namespace Temp4 +{ + //^12 + class Particle + { + // Multiple ways to initialize. + void init(double x, double y); + void init(double x, double y, double angle); + void init(double x, double y, double xVel, double yVel); + }; + + class ParticlePool + { + public: + void create(double x, double y) + { + // Forward to Particle... + } + + void create(double x, double y, double angle) + { + // Forward to Particle... + } + + void create(double x, double y, double xVel, double yVel) + { + // Forward to Particle... + } + }; + //^12 +} + +namespace Temp5 +{ + //^13 + class Particle + { + public: + // Multiple ways to initialize. + void init(double x, double y); + void init(double x, double y, double angle); + void init(double x, double y, double xVel, double yVel); + }; + + class ParticlePool + { + public: + Particle* create() + { + // Return reference to available particle... + //^omit + return &pool_[0]; + //^omit + } + private: + Particle pool_[100]; + }; + //^13 + + class Test + { + static void run() + { + //^14 + ParticlePool pool; + + pool.create()->init(1, 2); + pool.create()->init(1, 2, 0.3); + pool.create()->init(1, 2, 3.3, 4.4); + //^14 + + //^15 + Particle* particle = pool.create(); + if (particle != NULL) particle->init(1, 2); + //^15 + } + }; +}; + +// 64 characters --------------------------------------------------------| +void TestParticlePool() +{ + std::cout << "Object pool" << std::endl; + + Version1::ParticlePool pool = Version1::ParticlePool(); + + pool.create(0, 0, 1, 1, 10); + pool.create(1, 0, 1, 1, 10); + pool.create(2, 0, 1, 1, 10); + // pool.update(); +} \ No newline at end of file diff --git a/code/cpp/observer.h b/code/cpp/observer.h new file mode 100644 index 0000000..9d94bab --- /dev/null +++ b/code/cpp/observer.h @@ -0,0 +1,739 @@ +#ifndef observer_h +#define observer_h + +namespace ObserverPattern +{ + using namespace std; + + static const int SURFACE_BRIDGE = 0; + + class Entity { + public: + bool isHero() const { return false; } + bool isStandingOn(int surface) const { return false; } + bool isOnSurface() { return true; } + void accelerate(int force) {} + void update() {} + }; + + enum Event + { + EVENT_ENTITY_FELL + }; + + enum Achievement + { + ACHIEVEMENT_FELL_OFF_BRIDGE + }; + + static const int GRAVITY = 1; + static const int EVENT_START_FALL = 1; + + namespace Motivation + { + class Physics + { + public: + void updateEntity(Entity& entity); + void notify(Entity& entity, int event) {} + }; + + //^physics-update + void Physics::updateEntity(Entity& entity) + { + bool wasOnSurface = entity.isOnSurface(); + entity.accelerate(GRAVITY); + entity.update(); + if (wasOnSurface && !entity.isOnSurface()) + { + notify(entity, EVENT_START_FALL); + } + } + //^physics-update + } + + namespace Pattern + { + //^observer + class Observer + { + public: + virtual ~Observer() {} + virtual void onNotify(const Entity& entity, Event event) = 0; + }; + //^observer + + //^achievement-observer + class Achievements : public Observer + { + public: + virtual void onNotify(const Entity& entity, Event event) + { + switch (event) + { + case EVENT_ENTITY_FELL: + if (entity.isHero() && heroIsOnBridge_) + { + unlock(ACHIEVEMENT_FELL_OFF_BRIDGE); + } + break; + + // Handle other events, and update heroIsOnBridge_... + } + } + + private: + void unlock(Achievement achievement) + { + // Unlock if not already unlocked... + } + + bool heroIsOnBridge_; + }; + //^achievement-observer + + static const int MAX_OBSERVERS = 10; + + //^subject-list + //^subject-register + //^subject-notify + class Subject + { + //^omit subject-list + //^omit subject-notify + public: + void addObserver(Observer* observer) + { + // Add to array... + //^omit + observers_[numObservers_++] = observer; + //^omit + } + + void removeObserver(Observer* observer) + { + // Remove from array... + //^omit + int index; + for (index = 0; index < MAX_OBSERVERS; index++) + { + if (observers_[index] == observer) break; + } + + if (index < numObservers_) + { + // Shift the following ones up. + for (; index < numObservers_ - 1; index++) + { + observers_[index] = observers_[index + 1]; + } + + numObservers_--; + } + //^omit + } + + // Other stuff... + //^omit subject-notify + //^omit subject-register + protected: + void notify(const Entity& entity, Event event) + { + for (int i = 0; i < numObservers_; i++) + { + observers_[i]->onNotify(entity, event); + } + } + + // Other stuff... + //^omit subject-notify + + //^omit subject-list + private: + Observer* observers_[MAX_OBSERVERS]; + int numObservers_; + //^omit subject-register + //^omit subject-notify + }; + //^subject-list + //^subject-register + //^subject-notify + + //^physics-inherit + class Physics : public Subject + { + public: + void updateEntity(Entity& entity); + }; + //^physics-inherit + + class PhysicsEvent : public Observer + { + Subject entityFell_; + Subject& entityFell() { return entityFell_; } + + virtual void onNotify(const Entity& entity, Event event) {} + + void physicsEvent() + { + PhysicsEvent physics; + + //^physics-event + physics.entityFell() + .addObserver(this); + //^physics-event + } + }; + } + + namespace LinkedObservers + { + //^linked-observer + class Observer + { + friend class Subject; + + public: + Observer() + : next_(NULL) + {} + + // Other stuff... + //^omit + virtual void onNotify(const Entity& entity, Event event) {} + //^omit + private: + Observer* next_; + }; + //^linked-observer + + //^linked-subject + class Subject + { + Subject() + : head_(NULL) + {} + + // Methods... + //^omit + void addObserver(Observer* observer); + void removeObserver(Observer* observer); + void notify(const Entity& entity, Event event); + //^omit + private: + Observer* head_; + }; + //^linked-subject + + //^linked-add + void Subject::addObserver(Observer* observer) + { + observer->next_ = head_; + head_ = observer; + } + //^linked-add + + //^linked-remove + void Subject::removeObserver(Observer* observer) + { + if (head_ == observer) + { + head_ = observer->next_; + observer->next_ = NULL; + return; + } + + Observer* current = head_; + while (current != NULL) + { + if (current->next_ == observer) + { + current->next_ = observer->next_; + observer->next_ = NULL; + return; + } + + current = current->next_; + } + } + //^linked-remove + + //^linked-notify + void Subject::notify(const Entity& entity, Event event) + { + Observer* observer = head_; + while (observer != NULL) + { + observer->onNotify(entity, event); + observer = observer->next_; + } + } + //^linked-notify + } + + namespace One + { + class Observable; + + class Observer + { + friend class Observable; + + public: + bool isObserving() const { return observable_ != NULL; } + + void observe(Observable& observable); + void detach(); + + protected: + Observer() + : prev_(this), + next_(this) + {} + + virtual ~Observer() + { + detach(); + } + + virtual void onNotify(Observable& observable) = 0; + + private: + // The Observable this Observer is watching. + Observable* observable_ = NULL; + + // The next and previous nodes in the circular linked list + // observers of observable_. + Observer* prev_; + Observer* next_; + }; + + class Observable + { + friend class Observer; + + public: + bool hasObserver() const { return observer_ != NULL; } + + protected: + Observable() + : observer_(NULL) + {} + + virtual ~Observable() + { + // Detach all of the observers. + while (observer_ != NULL) observer_->detach(); + } + + void notify() + { + if (observer_ == NULL) return; + + Observer* observer = observer_; + do + { + observer->onNotify(*this); + observer = observer->next_; + } + while (observer != observer_); + } + + // The first in the linked list of observers of this. + Observer* observer_; + }; + + void Observer::observe(Observable& observable) + { + // Stop observing what it was previously observing. + detach(); + + if (observable.observer_ == NULL) + { + // The first observer. + observable.observer_ = this; + } + else + { + // Already have other observers, so link it in at the end of the + // list. + prev_ = observable.observer_->prev_; + next_ = observable.observer_; + + observable.observer_->prev_->next_ = this; + observable.observer_->prev_ = this; + } + + observable_ = &observable; + } + + void Observer::detach() + { + if (observable_ == NULL) return; + + // Make sure the observable itself isn't pointing at this node. + if (observable_->observer_ == this) + { + if (next_ == this) + { + // This is the only observer, so just clear it. + observable_->observer_ = NULL; + } + else + { + // Advance the next node. + observable_->observer_ = next_; + } + } + + // Unlink this observer from the list. + prev_->next_ = next_; + next_->prev_ = prev_; + + prev_ = this; + next_ = this; + observable_ = NULL; + } + + class Noise : public Observable + { + public: + Noise(const char* name) + : name_(name) + {} + + void sound() + { + cout << name_ << "!" << endl; + notify(); + } + + private: + const char* name_; + }; + + class Ear : public Observer + { + public: + Ear(const char* name) + : name_(name) + {} + + int numObserved = 0; + + protected: + virtual void onNotify(Observable & observable) + { + numObserved++; + cout << name_ << " heard it!" << endl; + } + + private: + const char* name_; + }; + + void destructSoloObserverTest() + { + Ear* ear = new Ear("ear"); + Noise noise("beep"); + ear->observe(noise); + + delete ear; + ASSERT(!noise.hasObserver()); + + noise.sound(); + } + + void destructMultipleObserverTest() + { + Ear* ear1 = new Ear("ear1"); + Ear* ear2 = new Ear("ear2"); + Noise noise("beep"); + ear1->observe(noise); + ear2->observe(noise); + + delete ear2; + ASSERT(noise.hasObserver()); + + delete ear1; + ASSERT(!noise.hasObserver()); + + noise.sound(); + } + + void destructObservableTest() + { + Ear ear1("ear1"); + Ear ear2("ear2"); + Noise* noise = new Noise("beep"); + ear1.observe(*noise); + ear2.observe(*noise); + + delete noise; + ASSERT(!ear1.isObserving()); + ASSERT(!ear2.isObserving()); + } + + void notifyTest() + { + Noise noise1("beep"); + Ear ear1("one"); + Ear ear2("two"); + Ear ear3("three"); + + noise1.sound(); + ASSERT(ear1.numObserved == 0); + ASSERT(ear2.numObserved == 0); + ASSERT(ear3.numObserved == 0); + + ear1.observe(noise1); + noise1.sound(); + ASSERT(ear1.numObserved == 1); + ASSERT(ear2.numObserved == 0); + ASSERT(ear3.numObserved == 0); + + ear2.observe(noise1); + noise1.sound(); + ASSERT(ear1.numObserved == 2); + ASSERT(ear2.numObserved == 1); + ASSERT(ear3.numObserved == 0); + + ear3.observe(noise1); + noise1.sound(); + ASSERT(ear1.numObserved == 3); + ASSERT(ear2.numObserved == 2); + ASSERT(ear3.numObserved == 1); + + ear2.detach(); + noise1.sound(); + ASSERT(ear1.numObserved == 4); + ASSERT(ear2.numObserved == 2); + ASSERT(ear3.numObserved == 2); + + ear1.detach(); + noise1.sound(); + ASSERT(ear1.numObserved == 4); + ASSERT(ear2.numObserved == 2); + ASSERT(ear3.numObserved == 3); + + ear3.detach(); + noise1.sound(); + ASSERT(ear1.numObserved == 4); + ASSERT(ear2.numObserved == 2); + ASSERT(ear3.numObserved == 3); + } + + void observeTest() + { + Ear ear("ear"); + Noise beep("beep"); + Noise boop("boop"); + + ear.observe(beep); + beep.sound(); + ASSERT(ear.numObserved == 1); + boop.sound(); + ASSERT(ear.numObserved == 1); + + // Should stop listening to beep. + ear.observe(boop); + beep.sound(); + ASSERT(ear.numObserved == 1); + boop.sound(); + ASSERT(ear.numObserved == 2); + } + + void test() + { + destructSoloObserverTest(); + destructMultipleObserverTest(); + destructObservableTest(); + notifyTest(); + observeTest(); + } + } + + namespace Pool + { + class Binding; + class BindingPool; + class Listener; + + class Event + { + public: + Event(BindingPool& pool) + : pool_(pool), + binding_(NULL) + {} + + void addListener(Listener& listener); + void removeListener(Listener& listener); + + void send(const char* data); + + private: + BindingPool& pool_; + Binding* binding_; + }; + + class Listener + { + public: + Listener(const char* name) + : name_(name) + {} + + int numEvents() const; + + void receive(const char* data) + { + cout << name_ << " received " << data << endl; + } + + private: + const char* name_; + }; + + class Binding + { + friend class BindingPool; + friend class Event; + + public: + Binding() + : listener_(NULL), + next_(NULL) + {} + + private: + Listener* listener_; + + // If the binding is in use, this will point to the binding for the + // next listener. Otherwise, it will point to the next free binding. + Binding* next_; + }; + + class BindingPool + { + public: + BindingPool() + { + // Build the free list. + free_ = &bindings_[0]; + for (int i = 0; i < POOL_SIZE - 1; i++) + { + bindings_[i].next_ = &bindings_[i + 1]; + } + } + + Binding* newBinding() + { + // TODO(bob): Make sure there is a free one. + // Remove the head of the free list. + Binding* binding = free_; + free_ = free_->next_; + return binding; + } + + private: + static const int POOL_SIZE = 100; + + Binding bindings_[POOL_SIZE]; + + // Pointer to the first binding in the list of free bindings. + Binding* free_; + }; + + + void Event::addListener(Listener& listener) + { + // TODO(bob): Add to end of list. + Binding* binding = pool_.newBinding(); + binding->listener_ = &listener; + binding->next_ = binding_; + binding_ = binding; + } + + void Event::removeListener(Listener& listener) + { + // TODO(bob): Implement me! + } + + void Event::send(const char* data) + { + Binding* binding = binding_; + while (binding != NULL) + { + binding->listener_->receive(data); + binding = binding->next_; + } + } + + int Listener::numEvents() const + { + return 0; // ? + } + + // TODO(bob): Destructors for all of these types. + + void destructEventTest() + { + BindingPool pool; + + Event* event1 = new Event(pool); + Event* event2 = new Event(pool); + + Listener listener1("listener 1"); + Listener listener2("listener 2"); + + event1->addListener(listener1); + event1->addListener(listener2); + event2->addListener(listener1); + event2->addListener(listener2); + + ASSERT(listener1.numEvents() == 2); + ASSERT(listener2.numEvents() == 2); + + delete event1; + + ASSERT(listener1.numEvents() == 1); + ASSERT(listener2.numEvents() == 1); + + delete event2; + + ASSERT(listener1.numEvents() == 0); + ASSERT(listener2.numEvents() == 0); + } + + void test() + { + destructEventTest(); + + BindingPool pool; + + Event event1(pool); + Event event2(pool); + + Listener listener1("listener 1"); + Listener listener2("listener 2"); + Listener listener3("listener 3"); + + event1.addListener(listener1); + event1.addListener(listener2); + event2.addListener(listener2); + event2.addListener(listener3); + + event1.send("first"); + event2.send("second"); + } + } + + void test() + { + //One::test(); + Pool::test(); + } +} + +#endif diff --git a/code/cpp/prototype.h b/code/cpp/prototype.h new file mode 100644 index 0000000..38aa8da --- /dev/null +++ b/code/cpp/prototype.h @@ -0,0 +1,190 @@ +#ifndef cpp_prototype_h +#define cpp_prototype_h + +namespace PrototypePattern +{ + namespace Classes + { + //^monster-classes + class Monster + { + // Stuff... + }; + + class Ghost : public Monster {}; + class Demon : public Monster {}; + class Sorcerer : public Monster {}; + //^monster-classes + + //^spawner-classes + class Spawner + { + public: + virtual ~Spawner() {} + virtual Monster* spawnMonster() = 0; + }; + + class GhostSpawner : public Spawner + { + public: + virtual Monster* spawnMonster() + { + return new Ghost(); + } + }; + + class DemonSpawner : public Spawner + { + public: + virtual Monster* spawnMonster() + { + return new Demon(); + } + }; + + // You get the idea... + //^spawner-classes + } + + namespace Clone + { + //^virtual-clone + class Monster + { + public: + virtual ~Monster() {} + virtual Monster* clone() = 0; + + // Other stuff... + }; + //^virtual-clone + + //^clone-ghost + class Ghost : public Monster { + public: + Ghost(int health, int speed) + : health_(health), + speed_(speed) + {} + + virtual Monster* clone() + { + return new Ghost(health_, speed_); + } + + private: + int health_; + int speed_; + }; + //^clone-ghost + + + //^spawner-clone + class Spawner + { + public: + Spawner(Monster* prototype) + : prototype_(prototype) + {} + + Monster* spawnMonster() + { + return prototype_->clone(); + } + + private: + Monster* prototype_; + }; + //^spawner-clone + + void test() + { + //^spawn-ghost-clone + Monster* ghostPrototype = new Ghost(15, 3); + Spawner* ghostSpawner = new Spawner(ghostPrototype); + //^spawn-ghost-clone + use(ghostSpawner); + } + } + + namespace Callbacks + { + class Monster + { + // Stuff... + }; + + class Ghost : public Monster {}; + + //^callback + Monster* spawnGhost() + { + return new Ghost(); + } + //^callback + + //^spawner-callback + typedef Monster* (*SpawnCallback)(); + + class Spawner + { + public: + Spawner(SpawnCallback spawn) + : spawn_(spawn) + {} + + Monster* spawnMonster() + { + return spawn_(); + } + + private: + SpawnCallback spawn_; + }; + //^spawner-callback + + void test() + { + //^spawn-ghost-callback + Spawner* ghostSpawner = new Spawner(spawnGhost); + //^spawn-ghost-callback + use(ghostSpawner); + } + } + + namespace Templates + { + class Monster + { + // Stuff... + }; + + class Ghost : public Monster {}; + + //^templates + class Spawner + { + public: + virtual ~Spawner() {} + virtual Monster* spawnMonster() = 0; + }; + + template + class SpawnerFor : public Spawner + { + public: + virtual Monster* spawnMonster() { return new T(); } + }; + //^templates + + void test() + { + //^use-templates + Spawner* ghostSpawner = new SpawnerFor(); + //^use-templates + use(ghostSpawner); + } + } +} + +#endif diff --git a/code/cpp/service-locator.h b/code/cpp/service-locator.h new file mode 100644 index 0000000..130f71f --- /dev/null +++ b/code/cpp/service-locator.h @@ -0,0 +1,247 @@ +#include + +class AudioSystem +{ +public: + static void playSound(int id) {} + static AudioSystem* instance() { return NULL; } +}; + +void example() +{ + int VERY_LOUD_BANG = 0; + + //^15 + // Use a static class? + AudioSystem::playSound(VERY_LOUD_BANG); + + // Or maybe a singleton? + AudioSystem::instance()->playSound(VERY_LOUD_BANG); + //^15 +} + +//^9 +class Audio +{ +public: + virtual ~Audio() {} + virtual void playSound(int soundID) = 0; + virtual void stopSound(int soundID) = 0; + virtual void stopAllSounds() = 0; +}; +//^9 + +//^10 +class ConsoleAudio : public Audio +{ +public: + virtual void playSound(int soundID) + { + // Play sound using console audio api... + } + + virtual void stopSound(int soundID) + { + // Stop sound using console audio api... + } + + virtual void stopAllSounds() + { + // Stop all sounds using console audio api... + } +}; +//^10 + +//^12 +class LoggedAudio : public Audio +{ +public: + LoggedAudio(Audio &wrapped) + : wrapped_(wrapped) + {} + + virtual void playSound(int soundID) + { + log("play sound"); + wrapped_.playSound(soundID); + } + + virtual void stopSound(int soundID) + { + log("stop sound"); + wrapped_.stopSound(soundID); + } + + virtual void stopAllSounds() + { + log("stop all sounds"); + wrapped_.stopAllSounds(); + } + +private: + void log(const char* message) + { + // Code to log message... + } + + Audio &wrapped_; +}; +//^12 + +// design decisions / di +namespace Version1 +{ + //^1 + class Locator + { + public: + static Audio* getAudio() { return service_; } + + static void provide(Audio* service) + { + service_ = service; + } + + private: + static Audio* service_; + }; + //^1 + + Audio *Locator::service_; + + void initGame() + { + //^11 + ConsoleAudio *audio = new ConsoleAudio(); + Locator::provide(audio); + //^11 + } + + void someGameCode() + { + int VERY_LOUD_BANG = 0; + //^5 + Audio *audio = Locator::getAudio(); + audio->playSound(VERY_LOUD_BANG); + //^5 + } +} + +// design decisions / compile time +namespace Version2 +{ + class DebugAudio: public Audio + { + public: + virtual void playSound(int soundID) { /* Do nothing. */ } + virtual void stopSound(int soundID) { /* Do nothing. */ } + virtual void stopAllSounds() { /* Do nothing. */ } + }; + class ReleaseAudio: public DebugAudio {}; + + //^2 + class Locator + { + public: + static Audio& getAudio() { return service_; } + + private: + #if DEBUG + static DebugAudio service_; + #else + static ReleaseAudio service_; + #endif + }; + //^2 +} + +// design decisions / scope +namespace Version3 +{ + //^3 + class Base + { + // Code to locate service and set service_... + + protected: + // Derived classes can use service + static Audio& getAudio() { return *service_; } + + private: + static Audio* service_; + }; + //^3 +} + +namespace Version4 +{ + //^4 + class Locator + { + public: + static Audio& getAudio() + { + Audio* service = NULL; + + // Code here to locate service... + + assert(service != NULL); + return *service; + } + }; + //^4 +} + +namespace Version5 +{ + //^7 + class NullAudio: public Audio + { + public: + virtual void playSound(int soundID) { /* Do nothing. */ } + virtual void stopSound(int soundID) { /* Do nothing. */ } + virtual void stopAllSounds() { /* Do nothing. */ } + }; + //^7 + + //^8 + class Locator + { + public: + static void initialize() { service_ = &nullService_; } + + static Audio& getAudio() { return *service_; } + + static void provide(Audio* service) + { + if (service == NULL) + { + // Revert to null service. + service_ = &nullService_; + } + else + { + service_ = service; + } + } + + private: + static Audio* service_; + static NullAudio nullService_; + }; + //^8 + + Audio *Locator::service_ = NULL; + NullAudio Locator::nullService_; + + //^13 + void enableAudioLogging() + { + // Decorate the existing service. + Audio *service = new LoggedAudio(Locator::getAudio()); + + // Swap it in. + Locator::provide(service); + } + //^13 +} \ No newline at end of file diff --git a/code/cpp/singleton.h b/code/cpp/singleton.h new file mode 100644 index 0000000..e37b6dc --- /dev/null +++ b/code/cpp/singleton.h @@ -0,0 +1,353 @@ +#include + +namespace Singleton1 +{ + //^1 + class FileSystem + { + public: + static FileSystem& instance() + { + // Lazy initialize. + if (instance_ == NULL) instance_ = new FileSystem(); + return *instance_; + } + + private: + FileSystem() {} + + static FileSystem* instance_; + }; + //^1 + + FileSystem* FileSystem::instance_; + + void test() + { + if (&FileSystem::instance() == NULL) + { + std::cout << "singleton is null!" << std::endl; + } + else + { + std::cout << "singleton is ok" << std::endl; + } + } +} + +namespace SingletonStatic +{ + //^local-static + class FileSystem + { + public: + static FileSystem& instance() + { + static FileSystem *instance = new FileSystem(); + return *instance; + } + + private: + FileSystem() {} + }; + //^local-static + + void test() + { + if (&FileSystem::instance() == NULL) + { + std::cout << "singleton is null!" << std::endl; + } + else + { + std::cout << "singleton is ok" << std::endl; + } + } +} + +namespace Singleton2 +{ + //^2 + class FileSystem + { + public: + virtual ~FileSystem() {} + virtual char* readFile(char* path) = 0; + virtual void writeFile(char* path, char* contents) = 0; + }; + //^2 + + //^derived-file-systems + class PS3FileSystem : public FileSystem + { + public: + virtual char* readFile(char* path) + { + // Use Sony file IO API... + //^omit + return NULL; + //^omit + } + + virtual void writeFile(char* path, char* contents) + { + // Use sony file IO API... + } + }; + + class WiiFileSystem : public FileSystem + { + public: + virtual char* readFile(char* path) + { + // Use Nintendo file IO API... + //^omit + return NULL; + //^omit + } + + virtual void writeFile(char* path, char* contents) + { + // Use Nintendo file IO API... + } + }; + //^derived-file-systems +} + +namespace Singleton3 +{ +#define PLAYSTATION3 1 +#define WII 2 +#define PLATFORM PLAYSTATION3 + + class PS3FileSystem; + class WiiFileSystem; + + //^3 + class FileSystem + { + public: + static FileSystem& instance(); + + virtual ~FileSystem() {} + virtual char* readFile(char* path) = 0; + virtual void writeFile(char* path, char* contents) = 0; + + protected: + FileSystem() {} + }; + //^3 + + class PS3FileSystem : public FileSystem + { + virtual char* readFile(char* path) + { + return NULL; + } + + virtual void writeFile(char* path, char* contents) {} + }; + + class WiiFileSystem : public FileSystem + { + virtual char* readFile(char* path) + { + return NULL; + } + + virtual void writeFile(char* path, char* contents) {} + }; + + //^4 + FileSystem& FileSystem::instance() + { + #if PLATFORM == PLAYSTATION3 + static FileSystem *instance = new PS3FileSystem(); + #elif PLATFORM == WII + static FileSystem *instance = new WiiFileSystem(); + #endif + + return *instance; + } + //^4 +} + +namespace Singleton4 +{ + //^5 + class FileSystem + { + public: + static FileSystem& instance() { return instance_; } + + private: + FileSystem() {} + + static FileSystem instance_; + }; + //^5 + + FileSystem FileSystem::instance_ = FileSystem(); +} + +namespace Singleton5 +{ + //^6 + class FileSystem + { + public: + FileSystem() + { + assert(!instantiated_); + instantiated_ = true; + } + + ~FileSystem() { instantiated_ = false; } + + private: + static bool instantiated_; + }; + + bool FileSystem::instantiated_ = false; + //^6 +} + +namespace Singleton7 +{ + #define SCREEN_WIDTH 100 + #define SCREEN_HEIGHT 100 + + //^8 + class Bullet + { + public: + int getX() const { return x_; } + int getY() const { return y_; } + + void setX(int x) { x_ = x; } + void setY(int y) { y_ = y; } + + private: + int x_, y_; + }; + + class BulletManager + { + public: + Bullet* create(int x, int y) + { + Bullet* bullet = new Bullet(); + bullet->setX(x); + bullet->setY(y); + + return bullet; + } + + bool isOnScreen(Bullet& bullet) + { + return bullet.getX() >= 0 && + bullet.getX() < SCREEN_WIDTH && + bullet.getY() >= 0 && + bullet.getY() < SCREEN_HEIGHT; + } + + void move(Bullet& bullet) + { + bullet.setX(bullet.getX() + 5); + } + }; + //^8 +} + +namespace Singleton8 +{ + //^9 + class Bullet + { + public: + Bullet(int x, int y) : x_(x), y_(y) {} + + bool isOnScreen() + { + return x_ >= 0 && x_ < SCREEN_WIDTH && + y_ >= 0 && y_ < SCREEN_HEIGHT; + } + + void move() { x_ += 5; } + + private: + int x_, y_; + }; + //^9 +} + +namespace Singleton9 +{ + class Log + { + public: + virtual ~Log() {} + virtual void write(const char* text) = 0; + }; + + //^10 + class GameObject + { + protected: + Log& getLog() { return log_; } + + private: + static Log& log_; + }; + + class Enemy : public GameObject + { + void doSomething() + { + getLog().write("I can log!"); + } + }; + //^10 +} + +namespace Singleton10 +{ + class Log {}; + class FileSystem {}; + class AudioPlayer + { + public: + virtual void play(int id) = 0; + }; + + //^11 + class Game + { + public: + static Game& instance() { return instance_; } + + // Functions to set log_, et. al. ... + + Log& getLog() { return *log_; } + FileSystem& getFileSystem() { return *fileSystem_; } + AudioPlayer& getAudioPlayer() { return *audioPlayer_; } + + private: + static Game instance_; + + Log *log_; + FileSystem *fileSystem_; + AudioPlayer *audioPlayer_; + }; + //^11 + + Game Game::instance_ = Game(); + + void foo() + { + int VERY_LOUD_BANG = 0; + //^12 + Game::instance().getAudioPlayer().play(VERY_LOUD_BANG); + //^12 + } +} diff --git a/code/cpp/spatial-partition.h b/code/cpp/spatial-partition.h new file mode 100644 index 0000000..4b9c6bf --- /dev/null +++ b/code/cpp/spatial-partition.h @@ -0,0 +1,577 @@ +#include +#include "expect.h" + +namespace SpatialPartition +{ + class Unit + { + public: + Unit(const char* name, int position) + : name(name), + position_(position), + hit(NULL) + {} + + const char* name; + Unit* hit; + + int position() const { return position_; } + private: + int position_; + }; + + class Vector + { + public: + Vector(int position) + : position(position) + {} + + int position; + }; + + bool operator==(int left, Vector right) + { + return left == right.position; + } + + namespace NaiveCollision + { + std::vector > hits; + + void handleAttack(Unit* a, Unit* b) + { + hits.push_back(std::make_pair(a, b)); + } + + //^pairwise + void handleMelee(Unit* units[], int numUnits) + { + for (int a = 0; a < numUnits - 1; a++) + { + for (int b = a + 1; b < numUnits; b++) + { + if (units[a]->position() == units[b]->position()) + { + handleAttack(units[a], units[b]); + } + } + } + } + //^pairwise + + void test() + { + Unit* units[5]; + units[0] = new Unit("a", 1); + units[1] = new Unit("b", 2); + units[2] = new Unit("c", 3); + units[3] = new Unit("d", 2); + units[4] = new Unit("e", 1); + handleMelee(units, 5); + EXPECT(hits[0].first == units[0]); + EXPECT(hits[0].second == units[4]); + EXPECT(hits[1].first == units[1]); + EXPECT(hits[1].second == units[3]); + } + } + + namespace SimpleUnit + { + class Grid; + + //^unit-simple + class Unit + { + friend class Grid; + + public: + Unit(Grid* grid, double x, double y) + : grid_(grid), + x_(x), + y_(y) + {} + + void move(double x, double y); + + private: + double x_, y_; + Grid* grid_; + }; + //^unit-simple + } + + namespace SimpleGrid + { + class Grid; + + //^grid-simple + class Grid + { + public: + Grid() + { + // Clear the grid. + for (int x = 0; x < NUM_CELLS; x++) + { + for (int y = 0; y < NUM_CELLS; y++) + { + cells_[x][y] = NULL; + } + } + } + + static const int NUM_CELLS = 10; + static const int CELL_SIZE = 20; + private: + Unit* cells_[NUM_CELLS][NUM_CELLS]; + }; + //^grid-simple + } + + namespace LinkedUnit + { + //^unit-linked + class Unit + { + // Previous code... + private: + Unit* prev_; + Unit* next_; + }; + //^unit-linked + } + + namespace AddToGrid + { + class Unit; + + //^add-decl + class Grid + { + public: + void add(Unit* unit); + + // Previous code... + //^omit + static const int NUM_CELLS = 10; + static const int CELL_SIZE = 20; + Unit* cells_[NUM_CELLS][NUM_CELLS]; + //^omit + }; + //^add-decl + + class Unit + { + public: + Unit(Grid* grid, double x, double y); + + // Previous code... + //^omit + friend class Grid; + + private: + double x_, y_; + + Grid* grid_; + + Unit* prev_; + Unit* next_; + //^omit + }; + + //^unit-ctor + Unit::Unit(Grid* grid, double x, double y) + : grid_(grid), + x_(x), + y_(y), + prev_(NULL), + next_(NULL) + { + grid_->add(this); + } + //^unit-ctor + + //^add + void Grid::add(Unit* unit) + { + // Determine which grid cell it's in. + int cellX = (int)(unit->x_ / Grid::CELL_SIZE); + int cellY = (int)(unit->y_ / Grid::CELL_SIZE); + + // Add to the front of list for the cell it's in. + unit->prev_ = NULL; + unit->next_ = cells_[cellX][cellY]; + cells_[cellX][cellY] = unit; + + if (unit->next_ != NULL) + { + unit->next_->prev_ = unit; + } + } + //^add + } + + namespace FixedGrid + { + class Unit; + + void handleAttack(Unit* unit, Unit* other) + { + + } + + class Grid + { + public: + Grid() + { + // Clear the grid. + for (int x = 0; x < NUM_CELLS; x++) + { + for (int y = 0; y < NUM_CELLS; y++) + { + cells_[x][y] = NULL; + } + } + } + + static const int CELL_SIZE = 20; + + void move(Unit* unit, double x, double y); + void add(Unit* unit); + + Unit* findAt(double x, double y); + + void handleMelee(); + void handleCell(Unit* unit); + + //^omit + void dump(); + //^omit + private: + static const int NUM_CELLS = 10; + + Unit* cells_[NUM_CELLS][NUM_CELLS]; + }; + + //^unit + class Unit + { + friend class Grid; + + public: + //^omit + const char* name; + //^omit + Unit(Grid* grid, double x, double y) + : grid_(grid), + //^omit + name(NULL), + //^omit + x_(x), + y_(y), + prev_(NULL), + next_(NULL) + { + grid_->add(this); + } + + void move(double x, double y); + + private: + double x_, y_; + + Grid* grid_; + + Unit* prev_; + Unit* next_; + }; + //^unit + + //^unit-move + void Unit::move(double x, double y) + { + grid_->move(this, x, y); + } + //^unit-move + + //^grid-move + void Grid::move(Unit* unit, double x, double y) + { + // See which cell it was in. + int oldCellX = (int)(unit->x_ / Grid::CELL_SIZE); + int oldCellY = (int)(unit->y_ / Grid::CELL_SIZE); + + // See which cell it's moving to. + int cellX = (int)(x / Grid::CELL_SIZE); + int cellY = (int)(y / Grid::CELL_SIZE); + + unit->x_ = x; + unit->y_ = y; + + // If it didn't change cells, we're done. + if (oldCellX == cellX && oldCellY == cellY) return; + + // Unlink it from the list of its old cell. + if (unit->prev_ != NULL) + { + unit->prev_->next_ = unit->next_; + } + + if (unit->next_ != NULL) + { + unit->next_->prev_ = unit->prev_; + } + + // If it's the head of a list, remove it. + if (cells_[oldCellX][oldCellY] == unit) + { + cells_[oldCellX][oldCellY] = unit->next_; + } + + // Add it back to the grid at its new cell. + add(unit); + } + //^grid-move + + void Grid::add(Unit* unit) + { + // Determine which grid cell it's in. + int cellX = (int)(unit->x_ / Grid::CELL_SIZE); + int cellY = (int)(unit->y_ / Grid::CELL_SIZE); + + // Add to the front of list for the cell its in. + unit->prev_ = NULL; + unit->next_ = cells_[cellX][cellY]; + cells_[cellX][cellY] = unit; + + if (unit->next_ != NULL) + { + unit->next_->prev_ = unit; + } + } + + Unit* Grid::findAt(double x, double y) + { + int cellX = (int)(x / Grid::CELL_SIZE); + int cellY = (int)(y / Grid::CELL_SIZE); + + Unit* unit = cells_[cellX][cellY]; + while (unit != NULL) + { + if (unit->x_ == x && unit->y_ == y) return unit; + unit = unit->next_; + } + + return NULL; + } + + void Grid::dump() + { + for (int y = 0; y < NUM_CELLS; y++) + { + for (int x = 0; x < NUM_CELLS; x++) + { + Unit* unit = cells_[x][y]; + if (unit == NULL) continue; + printf("%d, %d : ", x, y); + while (unit != NULL) + { + printf("%s ", unit->name); + unit = unit->next_; + } + printf("\n"); + } + } + printf("---\n"); + } + + // TODO(bob): Need tests for this. + //^grid-melee + void Grid::handleMelee() + { + for (int x = 0; x < NUM_CELLS; x++) + { + for (int y = 0; y < NUM_CELLS; y++) + { + handleCell(cells_[x][y]); + } + } + } + //^grid-melee + + //^handle-cell + void Grid::handleCell(Unit* unit) + { + while (unit != NULL) + { + Unit* other = unit->next_; + while (other != NULL) + { + if (unit->x_ == other->x_ && + unit->y_ == other->y_) + { + handleAttack(unit, other); + } + other = other->next_; + } + + unit = unit->next_; + } + } + //^handle-cell + + void test() + { + Grid grid; + + Unit a(&grid, 0, 0); a.name = "a"; + Unit b(&grid, 0, 0); b.name = "b"; + Unit c(&grid, 0, 0); c.name = "c"; + + b.move(50, 65); + c.move(55, 65); + a.move(20, 100); + c.move(22, 100); + + EXPECT(grid.findAt(20, 100) == &a); + EXPECT(grid.findAt(50, 65) == &b); + EXPECT(grid.findAt(22, 100) == &c); + } + } + + namespace AttackDistance + { + const int ATTACK_DISTANCE = 2; + + class Unit; + + void handleAttack(Unit* unit, Unit* other) + { + + } + + int distance(Unit* a, Unit* b) { return 3; } + + class Grid + { + public: + Grid() + { + // Clear the grid. + for (int x = 0; x < NUM_CELLS; x++) + { + for (int y = 0; y < NUM_CELLS; y++) + { + cells_[x][y] = NULL; + } + } + } + + static const int CELL_SIZE = 20; + + void move(Unit* unit, double x, double y); + void add(Unit* unit); + + Unit* findAt(double x, double y); + + void handleMelee(); + void handleCell(int x, int y); + void handleUnit(Unit* unit, Unit* other); + private: + static const int NUM_CELLS = 10; + + Unit* cells_[NUM_CELLS][NUM_CELLS]; + }; + + class Unit + { + friend class Grid; + + public: + Unit(Grid* grid, double x, double y) + : grid_(grid), + x_(x), + y_(y), + prev_(NULL), + next_(NULL) + { + grid_->add(this); + } + + void move(double x, double y); + + private: + double x_, y_; + + Grid* grid_; + + Unit* prev_; + Unit* next_; + }; + + // TODO(bob): Need tests for this. + void Grid::handleMelee() + { + for (int x = 0; x < NUM_CELLS; x++) + { + for (int y = 0; y < NUM_CELLS; y++) + { + handleCell(x, y); + } + } + } + + //^handle-neighbor + //^handle-cell-unit + void Grid::handleCell(int x, int y) + { + Unit* unit = cells_[x][y]; + while (unit != NULL) + { + // Handle other units in this cell. + handleUnit(unit, unit->next_); + //^omit handle-cell-unit + + // Also try the neighboring cells. + if (x > 0 && y > 0) handleUnit(unit, cells_[x - 1][y - 1]); + if (x > 0) handleUnit(unit, cells_[x - 1][y]); + if (y > 0) handleUnit(unit, cells_[x][y - 1]); + if (x > 0 && y < NUM_CELLS - 1) + { + handleUnit(unit, cells_[x - 1][y + 1]); + } + //^omit handle-cell-unit + + unit = unit->next_; + } + } + //^handle-cell-unit + //^handle-neighbor + + //^handle-unit + void Grid::handleUnit(Unit* unit, Unit* other) + { + while (other != NULL) + { + //^handle-distance + if (distance(unit, other) < ATTACK_DISTANCE) + { + handleAttack(unit, other); + } + //^handle-distance + + other = other->next_; + } + } + //^handle-unit + } + + void test() + { + printf("Testing Spatial Partition\n"); + NaiveCollision::test(); + FixedGrid::test(); + } +} diff --git a/code/cpp/state.h b/code/cpp/state.h new file mode 100644 index 0000000..0de1562 --- /dev/null +++ b/code/cpp/state.h @@ -0,0 +1,743 @@ +// +// state.h +// cpp +// +// Created by Bob Nystrom on 7/13/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#ifndef cpp_state_h +#define cpp_state_h + +namespace State +{ + enum Input + { + PRESS_A, + PRESS_B, + PRESS_LEFT, + PRESS_RIGHT, + PRESS_DOWN, + RELEASE_A, + RELEASE_DOWN, + }; + + enum Animate + { + IMAGE_JUMP, + IMAGE_DUCK, + IMAGE_STAND, + IMAGE_DIVE + }; + + static const int JUMP_VELOCITY = 1; + static const int MAX_CHARGE = 10; + + namespace Spaghetti1 + { + class Heroine + { + public: + void setGraphics(Animate animate) {} + void handleInput(Input input); + double yVelocity_; + }; + + //^spaghetti-1 + void Heroine::handleInput(Input input) + { + if (input == PRESS_B) + { + yVelocity_ = JUMP_VELOCITY; + setGraphics(IMAGE_JUMP); + } + } + //^spaghetti-1 + } + + namespace Spaghetti2 + { + class Heroine + { + public: + void setGraphics(Animate animate) {} + void handleInput(Input input); + double yVelocity_; + bool isJumping_; + }; + + //^spaghetti-2 + void Heroine::handleInput(Input input) + { + if (input == PRESS_B) + { + if (!isJumping_) + { + isJumping_ = true; + // Jump... + } + } + } + //^spaghetti-2 + } + + namespace Spaghetti3 + { + class Heroine + { + public: + void setGraphics(Animate animate) {} + void handleInput(Input input); + double yVelocity_; + bool isJumping_; + }; + + //^spaghetti-3 + void Heroine::handleInput(Input input) + { + if (input == PRESS_B) + { + // Jump if not jumping... + } + else if (input == PRESS_DOWN) + { + if (!isJumping_) + { + setGraphics(IMAGE_DUCK); + } + } + else if (input == RELEASE_DOWN) + { + setGraphics(IMAGE_STAND); + } + } + //^spaghetti-3 + } + + namespace Spaghetti4 + { + class Heroine + { + public: + void setGraphics(Animate animate) {} + void handleInput(Input input); + double yVelocity_; + bool isJumping_; + bool isDucking_; + }; + + //^spaghetti-4 + void Heroine::handleInput(Input input) + { + if (input == PRESS_B) + { + if (!isJumping_ && !isDucking_) + { + // Jump... + } + } + else if (input == PRESS_DOWN) + { + if (!isJumping_) + { + isDucking_ = true; + setGraphics(IMAGE_DUCK); + } + } + else if (input == RELEASE_DOWN) + { + if (isDucking_) + { + isDucking_ = false; + setGraphics(IMAGE_STAND); + } + } + } + //^spaghetti-4 + } + + namespace Spaghetti5 + { + class Heroine + { + public: + void setGraphics(Animate animate) {} + void handleInput(Input input); + double yVelocity_; + bool isJumping_; + bool isDucking_; + }; + + //^spaghetti-5 + void Heroine::handleInput(Input input) + { + if (input == PRESS_B) + { + if (!isJumping_ && !isDucking_) + { + // Jump... + } + } + else if (input == PRESS_DOWN) + { + if (!isJumping_) + { + isDucking_ = true; + setGraphics(IMAGE_DUCK); + } + else + { + isJumping_ = false; + setGraphics(IMAGE_DIVE); + } + } + else if (input == RELEASE_DOWN) + { + if (isDucking_) + { + // Stand... + } + } + } + //^spaghetti-5 + } + + namespace FSM + { + //^enum + enum State + { + STATE_STANDING, + STATE_JUMPING, + STATE_DUCKING, + STATE_DIVING + }; + //^enum + + class Heroine + { + public: + void setGraphics(Animate animate) {} + void superBomb() {} + void handleInput(Input input); + void update(); + double yVelocity_; + State state_; + int chargeTime_; + + void startDucking() + { + Input input = PRESS_DOWN; + + //^start-ducking + // In standing state: + if (input == PRESS_DOWN) + { + // Change state... + chargeTime_ = 0; + setGraphics(IMAGE_DUCK); + } + //^start-ducking + } + }; + + //^state-switch + void Heroine::handleInput(Input input) + { + switch (state_) + { + case STATE_STANDING: + if (input == PRESS_B) + { + state_ = STATE_JUMPING; + yVelocity_ = JUMP_VELOCITY; + setGraphics(IMAGE_JUMP); + } + else if (input == PRESS_DOWN) + { + state_ = STATE_DUCKING; + setGraphics(IMAGE_DUCK); + } + break; + + case STATE_JUMPING: + if (input == PRESS_DOWN) + { + state_ = STATE_DIVING; + setGraphics(IMAGE_DIVE); + } + break; + + case STATE_DUCKING: + if (input == RELEASE_DOWN) + { + state_ = STATE_STANDING; + setGraphics(IMAGE_STAND); + } + break; + //^omit + case STATE_DIVING: + break; + //^omit + } + } + + //^state-switch + + //^switch-update + void Heroine::update() + { + if (state_ == STATE_DUCKING) + { + chargeTime_++; + if (chargeTime_ > MAX_CHARGE) + { + superBomb(); + } + } + } + //^switch-update + } + + namespace FSMChargeTime + { + //^enum + enum State + { + STATE_STANDING, + STATE_JUMPING, + STATE_DUCKING, + STATE_DIVING + }; + //^enum + + class Heroine + { + public: + void setGraphics(Animate animate) {} + void superBomb() {} + void handleInput(Input input); + double yVelocity_; + State state_; + int chargeTime_; + }; + + //^state-switch-reset + void Heroine::handleInput(Input input) + { + switch (state_) + { + case STATE_STANDING: + if (input == PRESS_DOWN) + { + state_ = STATE_DUCKING; + chargeTime_ = 0; + setGraphics(IMAGE_DUCK); + } + // Handle other inputs... + break; + + // Other states... + //^omit + case STATE_JUMPING: + case STATE_DUCKING: + case STATE_DIVING: + break; + //^omit + } + } + //^state-switch-reset + } + + namespace StatePattern + { + class Heroine; + class JumpingState; + + //^heroine-state + class HeroineState + { + public: + //^omit + static JumpingState jumping; + //^omit + virtual ~HeroineState() {} + virtual void handleInput(Heroine& heroine, Input input) {} + virtual void update(Heroine& heroine) {} + }; + //^heroine-state + + class JumpingState : public HeroineState {}; + + class StandingState; + + //^gof-heroine + class Heroine + { + //^omit + friend class StandingState; + friend class JumpingState; + //^omit + public: + virtual void handleInput(Input input) + { + state_->handleInput(*this, input); + } + + virtual void update() + { + state_->update(*this); + } + + // Other methods... + //^omit + void setGraphics(Animate animate) {} + void superBomb() {} + //^omit + private: + HeroineState* state_; + //^omit + double yVelocity_; + //^omit + }; + //^gof-heroine + + //^ducking-state + class DuckingState : public HeroineState + { + public: + DuckingState() + : chargeTime_(0) + {} + + virtual void handleInput(Heroine& heroine, Input input) { + if (input == RELEASE_DOWN) + { + // Change to standing state... + heroine.setGraphics(IMAGE_STAND); + } + } + + virtual void update(Heroine& heroine) { + chargeTime_++; + if (chargeTime_ > MAX_CHARGE) + { + heroine.superBomb(); + } + } + + private: + int chargeTime_; + }; + //^ducking-state + } + + namespace InstancedStates + { + class Heroine; + + class HeroineState + { + public: + virtual ~HeroineState() {} + virtual HeroineState* handleInput(Heroine& heroine, Input input) + { + return NULL; + } + virtual void update(Heroine& heroine) {} + }; + + class Heroine + { + public: + virtual void handleInput(Input input); + + private: + HeroineState* state_; + }; + + class DuckingState : public HeroineState {}; + + //^swap-instance + void Heroine::handleInput(Input input) + { + HeroineState* state = state_->handleInput(*this, input); + if (state != NULL) + { + delete state_; + state_ = state; + } + } + //^swap-instance + + class StandingState : public HeroineState + { + public: + virtual HeroineState* handleInput(Heroine& heroine, Input input); + }; + + //^duck + HeroineState* StandingState::handleInput(Heroine& heroine, + Input input) + { + if (input == PRESS_DOWN) + { + // Other code... + return new DuckingState(); + } + + // Stay in this state. + return NULL; + } + //^duck + } + + namespace StaticStateInstances + { + class StandingState; + class DuckingState; + class JumpingState; + class DivingState; + + //^heroine-static-states + class HeroineState + { + public: + static StandingState standing; + static DuckingState ducking; + static JumpingState jumping; + static DivingState diving; + + // Other code... + }; + //^heroine-static-states + + class Heroine + { + friend class JumpingState; + public: + void setGraphics(Animate animate) {} + void changeState(HeroineState* state) {} + private: + HeroineState* state_; + }; + + class StandingState : public HeroineState {}; + class DuckingState : public HeroineState {}; + + class JumpingState : public HeroineState { + void handleInput(Heroine& heroine, Input input) + { + //^jump + if (input == PRESS_B) + { + heroine.state_ = &HeroineState::jumping; + heroine.setGraphics(IMAGE_JUMP); + } + //^jump + } + }; + class DivingState : public HeroineState {}; + } + + namespace EnterActionsBefore + { + class HeroineState {}; + + class Heroine + { + friend class JumpingState; + public: + void setGraphics(Animate animate) {} + void changeState(HeroineState* state) {} + }; + + class StandingState : public HeroineState {}; + class DuckingState : public HeroineState { + public: + HeroineState* handleInput(Heroine& heroine, Input input); + }; + + //^enter-standing-before + HeroineState* DuckingState::handleInput(Heroine& heroine, + Input input) + { + if (input == RELEASE_DOWN) + { + heroine.setGraphics(IMAGE_STAND); + return new StandingState(); + } + + // Other code... + //^omit + return NULL; + //^omit + } + //^enter-standing-before + } + + namespace EnterActions + { + class Heroine; + + class HeroineState + { + public: + virtual ~HeroineState() {} + virtual void enter(Heroine& heroine) {} + virtual HeroineState* handleInput(Heroine& heroine, Input input) + { + return NULL; + } + }; + + class Heroine + { + public: + void handleInput(Input input); + void setGraphics(Animate animate) {} + private: + HeroineState* state_; + }; + + //^standing-with-enter + class StandingState : public HeroineState + { + public: + virtual void enter(Heroine& heroine) + { + heroine.setGraphics(IMAGE_STAND); + } + + // Other code... + }; + //^standing-with-enter + + //^change-state + void Heroine::handleInput(Input input) + { + HeroineState* state = state_->handleInput(*this, input); + if (state != NULL) + { + delete state_; + state_ = state; + + // Call the enter action on the new state. + state_->enter(*this); + } + } + //^change-state + + class DuckingState : public HeroineState + { + public: + virtual HeroineState* handleInput(Heroine& heroine, Input input); + }; + + //^enter-standing + HeroineState* DuckingState::handleInput(Heroine& heroine, + Input input) + { + if (input == RELEASE_DOWN) + { + return new StandingState(); + } + + // Other code... + //^omit + return NULL; + //^omit + } + //^enter-standing + } + + namespace Concurrent + { + class Heroine; + + class HeroineState { + public: + void handleInput(Heroine& heroine, Input input) {} + }; + + //^two-states + class Heroine + { + // Other code... + //^omit + virtual void handleInput(Input input); + //^omit + + private: + HeroineState* state_; + HeroineState* equipment_; + }; + //^two-states + + //^handle-two-inputs + void Heroine::handleInput(Input input) + { + state_->handleInput(*this, input); + equipment_->handleInput(*this, input); + } + //^handle-two-inputs + } + + + namespace Hsm + { + class Heroine; + + class HeroineState { + public: + virtual void handleInput(Heroine& heroine, Input input); + }; + + class Heroine + { + void handleInput(Input input) {} + + private: + HeroineState* state_; + }; + + //^on-ground + class OnGroundState : public HeroineState + { + public: + virtual void handleInput(Heroine& heroine, Input input) + { + if (input == PRESS_B) + { + // Jump... + } + else if (input == PRESS_DOWN) + { + // Duck... + } + } + }; + //^on-ground + + //^duck-on-ground + class DuckingState : public OnGroundState + { + public: + virtual void handleInput(Heroine& heroine, Input input) + { + if (input == RELEASE_DOWN) + { + // Stand up... + } + else + { + // Didn't handle input, so walk up hierarchy. + OnGroundState::handleInput(heroine, input); + } + } + }; + //^duck-on-ground + } +} + +#endif diff --git a/code/cpp/subclass-sandbox.h b/code/cpp/subclass-sandbox.h new file mode 100644 index 0000000..0bda3b9 --- /dev/null +++ b/code/cpp/subclass-sandbox.h @@ -0,0 +1,330 @@ +typedef int SoundId; +typedef int ParticleType; + +const SoundId SOUND_SPROING = 1; +const SoundId SOUND_SWOOP = 1; +const SoundId SOUND_DIVE = 1; +const ParticleType PARTICLE_DUST = 1; +const ParticleType PARTICLE_SPARKLES = 1; + +namespace SimpleExample +{ + //^1 + class Superpower + { + public: + virtual ~Superpower() {} + + protected: + virtual void activate() = 0; + + void move(double x, double y, double z) + { + // Code here... + } + + void playSound(SoundId sound, double volume) + { + // Code here... + } + + void spawnParticles(ParticleType type, int count) + { + // Code here... + } + }; + //^1 + + //^2 + class SkyLaunch : public Superpower + { + protected: + virtual void activate() + { + // Spring into the air. + playSound(SOUND_SPROING, 1.0f); + spawnParticles(PARTICLE_DUST, 10); + move(0, 0, 20); + } + }; + //^2 +} + +namespace Elaborated +{ + //^3 + class Superpower + { + protected: + //^omit + virtual void activate() = 0; + void move(double x, double y, double z) {} + void playSound(SoundId sound, double volume) {} + void spawnParticles(ParticleType type, int count) {} + //^omit + double getHeroX() + { + // Code here... + //^omit + return 0; + //^omit + } + + double getHeroY() + { + // Code here... + //^omit + return 0; + //^omit + } + + double getHeroZ() + { + // Code here... + //^omit + return 0; + //^omit + } + + // Existing stuff... + }; + //^3 + + //^4 + class SkyLaunch : public Superpower + { + protected: + virtual void activate() + { + if (getHeroZ() == 0) + { + // On the ground, so spring into the air. + playSound(SOUND_SPROING, 1.0f); + spawnParticles(PARTICLE_DUST, 10); + move(0, 0, 20); + } + else if (getHeroZ() < 10.0f) + { + // Near the ground, so do a double jump. + playSound(SOUND_SWOOP, 1.0f); + move(0, 0, getHeroZ() + 20); + } + else + { + // Way up in the air, so do a dive attack. + playSound(SOUND_DIVE, 0.7f); + spawnParticles(PARTICLE_SPARKLES, 1); + move(0, 0, -getHeroZ()); + } + } + }; + //^4 +} + +namespace Forwarding +{ + struct SoundEngine + { + void play(SoundId sound, double volume) {} + }; + + SoundEngine soundEngine_; + + //^5 + void playSound(SoundId sound, double volume) + { + soundEngine_.play(sound, volume); + } + //^5 +} + +namespace HelperClassBefore +{ + //^6 + class Superpower + { + protected: + void playSound(SoundId sound, double volume) + { + // Code here... + } + + void stopSound(SoundId sound) + { + // Code here... + } + + void setVolume(SoundId sound) + { + // Code here... + } + + // Sandbox method and other operations... + }; + //^6 +}; + +namespace HelperClassAfter +{ + //^7 + class SoundPlayer + { + void playSound(SoundId sound, double volume) + { + // Code here... + } + + void stopSound(SoundId sound) + { + // Code here... + } + + void setVolume(SoundId sound) + { + // Code here... + } + }; + //^7 + + //^8 + class Superpower + { + protected: + SoundPlayer& getSoundPlayer() + { + return soundPlayer_; + } + + // Sandbox method and other operations... + + private: + SoundPlayer soundPlayer_; + }; + //^8 +} + +namespace PassToConstructor +{ + class ParticleSystem {}; + + //^pass-to-ctor-base + class Superpower + { + public: + Superpower(ParticleSystem* particles) + : particles_(particles) + {} + + // Sandbox method and other operations... + + private: + ParticleSystem* particles_; + }; + //^pass-to-ctor-base + + //^pass-to-ctor-sub + class SkyLaunch : public Superpower + { + public: + SkyLaunch(ParticleSystem* particles) + : Superpower(particles) + {} + }; + //^pass-to-ctor-sub +} + +namespace TwoStageInit +{ + class ParticleSystem {}; + + class Superpower + { + public: + void init(ParticleSystem* particles) {} + }; + + ParticleSystem* particles; + + class SkyLaunch : public Superpower {}; + + void foo() + { + //^9 + Superpower* power = new SkyLaunch(); + power->init(particles); + //^9 + } +} + +namespace TwoStageInitEncapsulated +{ + class ParticleSystem {}; + + class Superpower + { + public: + void init(ParticleSystem* audio) {} + }; + + class SkyLaunch : public Superpower {}; + + //^10 + Superpower* createSkyLaunch(ParticleSystem* particles) + { + Superpower* power = new SkyLaunch(); + power->init(particles); + return power; + } + //^10 +} + +namespace StaticState +{ + class ParticleSystem {}; + + //^11 + class Superpower + { + public: + static void init(ParticleSystem* particles) + { + particles_ = particles; + } + + // Sandbox method and other operations... + + private: + static ParticleSystem* particles_; + }; + //^11 +} + +namespace UseServiceLocator +{ + struct ParticleSystem + { + void spawn(ParticleType type, int count); + }; + + ParticleSystem particles; + + class Locator + { + public: + static ParticleSystem& getParticles() { return particles; } + }; + + //^12 + class Superpower + { + protected: + void spawnParticles(ParticleType type, int count) + { + ParticleSystem& particles = Locator::getParticles(); + particles.spawn(type, count); + } + + // Sandbox method and other operations... + }; + //^12 +} diff --git a/code/cpp/type-object.h b/code/cpp/type-object.h new file mode 100644 index 0000000..17e611b --- /dev/null +++ b/code/cpp/type-object.h @@ -0,0 +1,304 @@ +#include "common.h" + +namespace Subclasses +{ + //^1 + class Monster + { + public: + virtual ~Monster() {} + virtual const char* getAttack() = 0; + + protected: + Monster(int startingHealth) + : health_(startingHealth) + {} + + private: + int health_; // Current health. + }; + //^1 + + //^2 + class Dragon : public Monster + { + public: + Dragon() : Monster(230) {} + + virtual const char* getAttack() + { + return "The dragon breathes fire!"; + } + }; + + class Troll : public Monster + { + public: + Troll() : Monster(48) {} + + virtual const char* getAttack() + { + return "The troll clubs you!"; + } + }; + //^2 +} + +namespace NoInheritance +{ + //^3 + class Breed + { + public: + Breed(int health, const char* attack) + : health_(health), + attack_(attack) + {} + + int getHealth() { return health_; } + const char* getAttack() { return attack_; } + + private: + int health_; // Starting health. + const char* attack_; + }; + //^3 + + //^4 + class Monster + { + public: + Monster(Breed& breed) + : health_(breed.getHealth()), + breed_(breed) + {} + + const char* getAttack() + { + return breed_.getAttack(); + } + + private: + int health_; // Current health. + Breed& breed_; + }; + //^4 + + void testUsage() + { + Breed& someBreed = *(new Breed(123, "attack")); + + //^7 + Monster* monster = new Monster(someBreed); + //^7 + use(monster); + } +} + +namespace BreedCtor +{ + class Breed; + + class Monster + { + public: + Monster(Breed& breed) {} + }; + + //^5 + class Breed + { + public: + Monster* newMonster() { return new Monster(*this); } + + // Previous Breed code... + //^omit + Breed(int health, const char* attack) + : health_(health), + attack_(attack) + {} + + int getHealth() { return health_; } + const char* getAttack() { return attack_; } + + private: + int health_; // Starting health. + const char* attack_; + //^omit + }; + //^5 + + void testUsage() + { + Breed& someBreed = *(new Breed(1, "foo")); + + //^8 + Monster* monster = someBreed.newMonster(); + //^8 + use(monster); + } +} + +namespace BreedCtorMonster +{ + class Breed + { + public: + int getHealth() { return 0; } + const char* getAttack() { return "s"; } + }; + + //^6 + class Monster + { + friend class Breed; + + public: + const char* getAttack() { return breed_.getAttack(); } + + private: + Monster(Breed& breed) + : health_(breed.getHealth()), + breed_(breed) + {} + + int health_; // Current health. + Breed& breed_; + }; + //^6 +} + +namespace Inheritance +{ + //^9 + class Breed + { + public: + Breed(Breed* parent, int health, const char* attack) + : parent_(parent), + health_(health), + attack_(attack) + {} + + int getHealth(); + const char* getAttack(); + + private: + Breed* parent_; + int health_; // Starting health. + const char* attack_; + }; + //^9 + + //^10 + int Breed::getHealth() + { + // Override. + if (health_ != 0 || parent_ == NULL) return health_; + + // Inherit. + return parent_->getHealth(); + } + + const char* Breed::getAttack() + { + // Override. + if (attack_ != NULL || parent_ == NULL) return attack_; + + // Inherit. + return parent_->getAttack(); + } + //^10 +} + +namespace CopyDown +{ + class Breed + { + public: + //^copy-down + Breed(Breed* parent, int health, const char* attack) + : health_(health), + attack_(attack) + { + // Inherit non-overridden attributes. + if (parent != NULL) + { + if (health == 0) health_ = parent->getHealth(); + if (attack == NULL) attack_ = parent->getAttack(); + } + } + //^copy-down + + //^copy-down-access + int getHealth() { return health_; } + const char* getAttack() { return attack_; } + //^copy-down-access + + private: + int health_; // Starting health. + const char* attack_; + }; +} + +namespace ExposeBreed +{ + class Breed; + + //^11 + class Monster + { + public: + Breed& getBreed() { return breed_; } + + // Existing code... + //^omit + Breed& breed_; + //^omit + }; + //^11 +} + +namespace OverrideAttack +{ + class Breed + { + public: + Breed(const char* attack) + : attack_(attack) + {} + + const char* getAttack() { return attack_; } + + private: + const char* attack_; + }; + + class Monster + { + public: + Monster(Breed& breed) + : breed_(breed) + {} + + const char* getAttack(); + + private: + int health_; + Breed& breed_; + }; + + #define LOW_HEALTH 1 + + //^12 + const char* Monster::getAttack() + { + if (health_ < LOW_HEALTH) + { + return "The monster flails weakly."; + } + + return breed_.getAttack(); + } + //^12 +} \ No newline at end of file diff --git a/code/cpp/update-method.h b/code/cpp/update-method.h new file mode 100644 index 0000000..7904b4d --- /dev/null +++ b/code/cpp/update-method.h @@ -0,0 +1,325 @@ +// +// update-method.h +// cpp +// +// Created by Bob Nystrom on 8/6/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#ifndef cpp_update_method_h +#define cpp_update_method_h + +namespace UpdateMethod +{ + namespace Motivation + { + struct Entity + { + void shootLightning() {} + void setX(double x) {} + }; + + void refreshGame() {} + + void justPatrol() + { + Entity skeleton; + + //^just-patrol + while (true) + { + // Patrol right. + for (double x = 0; x < 100; x++) + { + skeleton.setX(x); + } + + // Patrol left. + for (double x = 100; x > 0; x--) + { + skeleton.setX(x); + } + } + //^just-patrol + } + + void patrolInLoop() + { + //^patrol-in-loop + Entity skeleton; + bool patrollingLeft = false; + double x = 0; + + // Main game loop: + while (true) + { + if (patrollingLeft) + { + x--; + if (x == 0) patrollingLeft = false; + } + else + { + x++; + if (x == 100) patrollingLeft = true; + } + + skeleton.setX(x); + + // Handle user input and render game... + } + //^patrol-in-loop + } + + void statues() + { + //^statues + // Skeleton variables... + Entity leftStatue; + Entity rightStatue; + int leftStatueFrames = 0; + int rightStatueFrames = 0; + + // Main game loop: + while (true) + { + // Skeleton code... + + if (++leftStatueFrames == 90) + { + leftStatueFrames = 0; + leftStatue.shootLightning(); + } + + if (++rightStatueFrames == 80) + { + rightStatueFrames = 0; + rightStatue.shootLightning(); + } + + // Handle user input and render game... + } + //^statues + } + } + + namespace KeepInMind + { + struct Entity + { + void setPosition(int x, int y) {} + void shootLightning() {} + void update() {} + }; + + static const int MAX_ENTITIES = 10; + + void refreshGame() {} + + void skipAdded() + { + int numObjects_ = 0; + Entity* objects_[MAX_ENTITIES]; + //^skip-added + int numObjectsThisTurn = numObjects_; + for (int i = 0; i < numObjectsThisTurn; i++) + { + objects_[i]->update(); + } + //^skip-added + } + + void skipRemoved() + { + int numObjects_ = 0; + Entity* objects_[MAX_ENTITIES]; + + //^skip-removed + for (int i = 0; i < numObjects_; i++) + { + objects_[i]->update(); + } + //^skip-removed + } + } + + namespace SampleCode + { + static const int MAX_ENTITIES = 10; + + //^entity-class + class Entity + { + public: + Entity() + : x_(0), y_(0) + {} + + virtual ~Entity() {} + virtual void update() = 0; + + double x() const { return x_; } + double y() const { return y_; } + + void setX(double x) { x_ = x; } + void setY(double y) { y_ = y; } + + private: + double x_; + double y_; + }; + //^entity-class + + //^game-world + class World + { + public: + World() + : numEntities_(0) + {} + + void gameLoop(); + + private: + Entity* entities_[MAX_ENTITIES]; + int numEntities_; + }; + //^game-world + + //^game-loop + void World::gameLoop() + { + while (true) + { + // Handle user input... + + // Update each entity. + //^update-component-entities + for (int i = 0; i < numEntities_; i++) + { + entities_[i]->update(); + } + //^update-component-entities + + // Physics and rendering... + } + } + //^game-loop + + //^skeleton + class Skeleton : public Entity + { + public: + Skeleton() + : patrollingLeft_(false) + {} + + virtual void update() + { + if (patrollingLeft_) + { + setX(x() - 1); + if (x() == 0) patrollingLeft_ = false; + } + else + { + setX(x() + 1); + if (x() == 100) patrollingLeft_ = true; + } + } + + private: + bool patrollingLeft_; + }; + //^skeleton + + //^statue + class Statue : public Entity + { + public: + Statue(int delay) + : frames_(0), + delay_(delay) + {} + + virtual void update() + { + if (++frames_ == delay_) + { + shootLightning(); + + // Reset the timer. + frames_ = 0; + } + } + + private: + int frames_; + int delay_; + + void shootLightning() + { + // Shoot the lightning... + } + }; + //^statue + } + + namespace ForwardToDelegate + { + class Entity; + + class Entity + { + public: + Entity* state_; + void update(); + }; + + //^forward + void Entity::update() + { + // Forward to state object. + state_->update(); + } + //^forward + } + + namespace VariableTimeStep + { + class Skeleton + { + public: + double x; + bool patrollingLeft_; + void update(double elapsed); + }; + + //^variable + void Skeleton::update(double elapsed) + { + if (patrollingLeft_) + { + x -= elapsed; + if (x <= 0) + { + patrollingLeft_ = false; + x = -x; + } + } + else + { + x += elapsed; + if (x >= 100) + { + patrollingLeft_ = true; + x = 100 - (x - 100); + } + } + } + //^variable + } + +} + +#endif diff --git a/code/dart/spatial-partition/pigeonhole_comparisons.dart b/code/dart/spatial-partition/pigeonhole_comparisons.dart new file mode 100644 index 0000000..6653373 --- /dev/null +++ b/code/dart/spatial-partition/pigeonhole_comparisons.dart @@ -0,0 +1,76 @@ +import 'dart:math'; + +const MAX_POS = 100; + +const NUM_UNITS = 200; +const NUM_RUNS = 100; + +final rand = new Random(); + +class Unit { + final int pos; + Unit(this.pos); +} + +List makeUnits(int seed) { + var rand = new Random(seed); + + var units = []; + for (var i = 0; i < NUM_UNITS; i++) { + units.add(new Unit(rand.nextInt(MAX_POS))); + } + + return units; +} + +slow() { + var compares = 0; + var hits = 0; + + for (var i = 0; i < NUM_RUNS; i++) { + var units = makeUnits(i); + + for (var a = 0; a < units.length - 1; a++) { + for (var b = a + 1; b < units.length; b++) { + compares++; + if (units[a].pos == units[b].pos) { + hits++; + } + } + } + } + + compares /= NUM_RUNS; + hits /= NUM_RUNS; + print("slow: $compares compares, $hits hits"); +} + +fast() { + var compares = 0; + var hits = 0; + + for (var i = 0; i < NUM_RUNS; i++) { + var units = makeUnits(i); + + var slots = new List.generate(MAX_POS, (_) => []); + for (var unit in units) slots[unit.pos].add(unit); + + for (var slot in slots) { + for (var a = 0; a < slot.length - 1; a++) { + for (var b = a + 1; b < slot.length; b++) { + compares++; + hits++; + } + } + } + } + + compares /= NUM_RUNS; + hits /= NUM_RUNS; + print("fast: $compares compares, $hits hits"); +} + +main() { + slow(); + fast(); +} \ No newline at end of file diff --git a/code/flyweight/tiles/tiles.xcodeproj/project.pbxproj b/code/flyweight/tiles/tiles.xcodeproj/project.pbxproj new file mode 100644 index 0000000..885dcc2 --- /dev/null +++ b/code/flyweight/tiles/tiles.xcodeproj/project.pbxproj @@ -0,0 +1,225 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 2985588017E5645E00BA78C0 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2985587F17E5645E00BA78C0 /* main.cpp */; }; + 2985588217E5645E00BA78C0 /* tiles.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2985588117E5645E00BA78C0 /* tiles.1 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2985587A17E5645E00BA78C0 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + 2985588217E5645E00BA78C0 /* tiles.1 in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2985587C17E5645E00BA78C0 /* tiles */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = tiles; sourceTree = BUILT_PRODUCTS_DIR; }; + 2985587F17E5645E00BA78C0 /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 2985588117E5645E00BA78C0 /* tiles.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = tiles.1; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2985587917E5645E00BA78C0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2985587317E5645E00BA78C0 = { + isa = PBXGroup; + children = ( + 2985587E17E5645E00BA78C0 /* tiles */, + 2985587D17E5645E00BA78C0 /* Products */, + ); + sourceTree = ""; + }; + 2985587D17E5645E00BA78C0 /* Products */ = { + isa = PBXGroup; + children = ( + 2985587C17E5645E00BA78C0 /* tiles */, + ); + name = Products; + sourceTree = ""; + }; + 2985587E17E5645E00BA78C0 /* tiles */ = { + isa = PBXGroup; + children = ( + 2985587F17E5645E00BA78C0 /* main.cpp */, + 2985588117E5645E00BA78C0 /* tiles.1 */, + ); + path = tiles; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2985587B17E5645E00BA78C0 /* tiles */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2985588517E5645E00BA78C0 /* Build configuration list for PBXNativeTarget "tiles" */; + buildPhases = ( + 2985587817E5645E00BA78C0 /* Sources */, + 2985587917E5645E00BA78C0 /* Frameworks */, + 2985587A17E5645E00BA78C0 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = tiles; + productName = tiles; + productReference = 2985587C17E5645E00BA78C0 /* tiles */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2985587417E5645E00BA78C0 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0460; + ORGANIZATIONNAME = "Bob Nystrom"; + }; + buildConfigurationList = 2985587717E5645E00BA78C0 /* Build configuration list for PBXProject "tiles" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 2985587317E5645E00BA78C0; + productRefGroup = 2985587D17E5645E00BA78C0 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2985587B17E5645E00BA78C0 /* tiles */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 2985587817E5645E00BA78C0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2985588017E5645E00BA78C0 /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2985588317E5645E00BA78C0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 2985588417E5645E00BA78C0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 2985588617E5645E00BA78C0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ENABLE_CPP_EXCEPTIONS = NO; + GCC_ENABLE_CPP_RTTI = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 2985588717E5645E00BA78C0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ENABLE_CPP_EXCEPTIONS = NO; + GCC_ENABLE_CPP_RTTI = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2985587717E5645E00BA78C0 /* Build configuration list for PBXProject "tiles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2985588317E5645E00BA78C0 /* Debug */, + 2985588417E5645E00BA78C0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2985588517E5645E00BA78C0 /* Build configuration list for PBXNativeTarget "tiles" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2985588617E5645E00BA78C0 /* Debug */, + 2985588717E5645E00BA78C0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2985587417E5645E00BA78C0 /* Project object */; +} diff --git a/code/flyweight/tiles/tiles/main.cpp b/code/flyweight/tiles/tiles/main.cpp new file mode 100644 index 0000000..7693f72 --- /dev/null +++ b/code/flyweight/tiles/tiles/main.cpp @@ -0,0 +1,279 @@ +// +// main.cpp +// tiles +// +// Created by Bob Nystrom on 9/14/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#include +#include + +#define ADD_OTHER_STUFF + +static const int SIZE = 128; + +struct timeval startTime; + +void startProfile() +{ + gettimeofday(&startTime, NULL); +} + +void endProfile(const char* message) +{ + struct timeval endTime; + gettimeofday(&endTime, NULL); + + endTime.tv_sec -= startTime.tv_sec; + endTime.tv_usec -= startTime.tv_usec; + + printf("%s: %lds %dμs\n", message, endTime.tv_sec, endTime.tv_usec); +} + +int pickTile(int x, int y) { + return (x * 7 + y * 13) % 4; +} + +namespace tileEnum +{ + enum Tile + { + TILE_GRASS = 0, + TILE_FOREST, + TILE_MOUNTAIN, + TILE_WATER + }; + + struct World + { + Tile tiles[SIZE * SIZE]; + World() + { + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + tiles[y * SIZE + x] = (Tile)pickTile(x, y); + } + } + } + + int addTiles() + { + int sum = 0; + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + switch (tiles[y * SIZE + x]) + { + case TILE_GRASS: sum += 1; break; + case TILE_FOREST: sum += 2; break; + case TILE_MOUNTAIN: sum += 3; break; + case TILE_WATER: sum += 4; break; + } + } + } + return sum; + } + }; +}; + +namespace tileByte +{ + enum Tile + { + TILE_GRASS = 0, + TILE_FOREST, + TILE_MOUNTAIN, + TILE_WATER + }; + + struct World + { + unsigned char tiles[SIZE * SIZE]; + World() + { + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + tiles[y * SIZE + x] = pickTile(x, y); + } + } + } + + int addTiles() + { + int sum = 0; + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + switch (tiles[y * SIZE + x]) + { + case TILE_GRASS: sum += 1; break; + case TILE_FOREST: sum += 2; break; + case TILE_MOUNTAIN: sum += 3; break; + case TILE_WATER: sum += 4; break; + } + } + } + return sum; + } + }; +}; + +namespace tileField +{ + struct Tile + { + int weight; +#ifdef ADD_OTHER_STUFF + int other[16]; +#endif + }; + + struct World + { + Tile tileTypes[4]; + + Tile* tiles[SIZE * SIZE]; + World() + { + tileTypes[0].weight = 1; + tileTypes[1].weight = 2; + tileTypes[2].weight = 3; + tileTypes[3].weight = 4; + + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + tiles[y * SIZE + x] = &tileTypes[pickTile(x, y)]; + } + } + } + + int addTiles() + { + int sum = 0; + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + sum += tiles[y * SIZE + x]->weight; + } + } + return sum; + } + }; +}; + +namespace tileVirtual +{ + struct Tile + { + virtual int weight() = 0; + }; + + struct GrassTile : public Tile + { + virtual int weight() { return 1; } + }; + + struct ForestTile : public Tile + { + virtual int weight() { return 2; } + }; + + struct MountainTile : public Tile + { + virtual int weight() { return 3; } + }; + + struct WaterTile : public Tile + { + virtual int weight() { return 4; } + }; + + struct World + { + GrassTile grass; + ForestTile forest; + MountainTile mountain; + WaterTile water; + Tile* tileTypes[4]; + + Tile* tiles[SIZE * SIZE]; + World() + { + tileTypes[0] = &grass; + tileTypes[1] = &forest; + tileTypes[2] = &mountain; + tileTypes[3] = &water; + + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + tiles[y * SIZE + x] = tileTypes[pickTile(x, y)]; + } + } + } + + int addTiles() + { + int sum = 0; + for (int y = 0; y < SIZE; y++) + { + for (int x = 0; x < SIZE; x++) + { + sum += tiles[y * SIZE + x]->weight(); + } + } + return sum; + } + }; +}; + +int main(int argc, const char * argv[]) +{ + for (int i = 0; i < 1; i++) + { + { + tileEnum::World world; + startProfile(); + int sum = world.addTiles(); + endProfile("tile enum"); + printf("%d\n", sum); + } + + { + tileByte::World world; + startProfile(); + int sum = world.addTiles(); + endProfile("tile byte"); + printf("%d\n", sum); + } + + { + tileField::World world; + startProfile(); + int sum = world.addTiles(); + endProfile("tile field"); + printf("%d\n", sum); + } + + { + tileVirtual::World world; + startProfile(); + int sum = world.addTiles(); + endProfile("tile virtual"); + printf("%d\n", sum); + } + } + + return 0; +} diff --git a/code/flyweight/tiles/tiles/tiles.1 b/code/flyweight/tiles/tiles/tiles.1 new file mode 100644 index 0000000..a25d52c --- /dev/null +++ b/code/flyweight/tiles/tiles/tiles.1 @@ -0,0 +1,79 @@ +.\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. +.\"See Also: +.\"man mdoc.samples for a complete listing of options +.\"man mdoc for the short list of editing options +.\"/usr/share/misc/mdoc.template +.Dd 9/14/13 \" DATE +.Dt tiles 1 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm tiles, +.\" The following lines are read in generating the apropos(man -k) database. Use only key +.\" words here as the database is built based on the words here and in the .ND line. +.Nm Other_name_for_same_program(), +.Nm Yet another name for the same program. +.\" Use .Nm macro to designate other names for the documented program. +.Nd This line parsed for whatis database. +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Op Fl abcd \" [-abcd] +.Op Fl a Ar path \" [-a path] +.Op Ar file \" [file] +.Op Ar \" [file ...] +.Ar arg0 \" Underlined argument - use .Ar anywhere to underline +arg2 ... \" Arguments +.Sh DESCRIPTION \" Section Header - required - don't modify +Use the .Nm macro to refer to your program throughout the man page like such: +.Nm +Underlining is accomplished with the .Ar macro like this: +.Ar underlined text . +.Pp \" Inserts a space +A list of items with descriptions: +.Bl -tag -width -indent \" Begins a tagged list +.It item a \" Each item preceded by .It macro +Description of item a +.It item b +Description of item b +.El \" Ends the list +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent \" Differs from above in tag removed +.It Fl a \"-a flag as a list item +Description of -a flag +.It Fl b +Description of -b flag +.El \" Ends the list +.Pp +.\" .Sh ENVIRONMENT \" May not be needed +.\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 +.\" .It Ev ENV_VAR_1 +.\" Description of ENV_VAR_1 +.\" .It Ev ENV_VAR_2 +.\" Description of ENV_VAR_2 +.\" .El +.Sh FILES \" File used or created by the topic of the man page +.Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact +.It Pa /usr/share/file_name +FILE_1 description +.It Pa /Users/joeuser/Library/really_long_file_name +FILE_2 description +.El \" Ends the list +.\" .Sh DIAGNOSTICS \" May not be needed +.\" .Bl -diag +.\" .It Diagnostic Tag +.\" Diagnostic informtion here. +.\" .It Diagnostic Tag +.\" Diagnostic informtion here. +.\" .El +.Sh SEE ALSO +.\" List links in ascending order by section, alphabetically within a section. +.\" Please do not reference files that do not exist without filing a bug report +.Xr a 1 , +.Xr b 1 , +.Xr c 1 , +.Xr a 2 , +.Xr b 2 , +.Xr a 3 , +.Xr b 3 +.\" .Sh BUGS \" Document known, unremedied bugs +.\" .Sh HISTORY \" Document history if command behaves in a unique manner \ No newline at end of file diff --git a/code/structure-of-arrays/activate/activate.xcodeproj/project.pbxproj b/code/structure-of-arrays/activate/activate.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e9f9745 --- /dev/null +++ b/code/structure-of-arrays/activate/activate.xcodeproj/project.pbxproj @@ -0,0 +1,222 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 296232521830ACD100407933 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 296232511830ACD100407933 /* main.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 296232431830ACB800407933 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 296232451830ACB800407933 /* activate */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = activate; sourceTree = BUILT_PRODUCTS_DIR; }; + 296232511830ACD100407933 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 296232531830ACED00407933 /* utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = utils.h; path = ../shared/utils.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 296232421830ACB800407933 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2962323C1830ACB800407933 = { + isa = PBXGroup; + children = ( + 296232531830ACED00407933 /* utils.h */, + 296232511830ACD100407933 /* main.cpp */, + 296232461830ACB800407933 /* Products */, + ); + sourceTree = ""; + }; + 296232461830ACB800407933 /* Products */ = { + isa = PBXGroup; + children = ( + 296232451830ACB800407933 /* activate */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 296232441830ACB800407933 /* activate */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2962324E1830ACB800407933 /* Build configuration list for PBXNativeTarget "activate" */; + buildPhases = ( + 296232411830ACB800407933 /* Sources */, + 296232421830ACB800407933 /* Frameworks */, + 296232431830ACB800407933 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = activate; + productName = activate; + productReference = 296232451830ACB800407933 /* activate */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2962323D1830ACB800407933 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Bob Nystrom"; + }; + buildConfigurationList = 296232401830ACB800407933 /* Build configuration list for PBXProject "activate" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 2962323C1830ACB800407933; + productRefGroup = 296232461830ACB800407933 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 296232441830ACB800407933 /* activate */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 296232411830ACB800407933 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 296232521830ACD100407933 /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2962324C1830ACB800407933 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 2962324D1830ACB800407933 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 2962324F1830ACB800407933 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 296232501830ACB800407933 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 296232401830ACB800407933 /* Build configuration list for PBXProject "activate" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2962324C1830ACB800407933 /* Debug */, + 2962324D1830ACB800407933 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2962324E1830ACB800407933 /* Build configuration list for PBXNativeTarget "activate" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2962324F1830ACB800407933 /* Debug */, + 296232501830ACB800407933 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2962323D1830ACB800407933 /* Project object */; +} diff --git a/code/structure-of-arrays/activate/main.cpp b/code/structure-of-arrays/activate/main.cpp new file mode 100644 index 0000000..c168c76 --- /dev/null +++ b/code/structure-of-arrays/activate/main.cpp @@ -0,0 +1,136 @@ +#include "utils.h" + +static const int NUM_FRAMES = 1000; +static const int NUM_ACTORS = 10000; +static const int NUM_FIELDS = 4; +static const int TOGGLE_RATE = 10; + +class Actor +{ +public: + Actor() + { + for (int i = 0; i < NUM_FIELDS; i++) data[i] = i; + } + + bool isActive = false; + + int data[NUM_FIELDS]; + + int update() + { + int sum = 0; + for (int i = 0; i < NUM_FIELDS; i++) sum += data[i]; + return sum; + } +}; + +class SmallActor +{ +public: + SmallActor() + { + for (int i = 0; i < NUM_FIELDS; i++) data[i] = i; + } + + int data[NUM_FIELDS]; + + int update() + { + int sum = 0; + for (int i = 0; i < NUM_FIELDS; i++) sum += data[i]; + return sum; + } +}; + +// A flat array of contiguous actors with randomly chosen ones active. +void testMixed() +{ + Actor* actors = new Actor[NUM_ACTORS]; + + // Enable half of them randomly. + int* indexes = shuffledArray(NUM_ACTORS); + for (int i = 0; i < NUM_ACTORS / 2; i++) + { + actors[indexes[i]].isActive = true; + } + delete [] indexes; + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_FRAMES; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + if (actors[j].isActive) + { + sum += actors[j].update(); + } + + // Randomly change active state. + if (randRange(1, TOGGLE_RATE) == 0) actors[j].isActive = !actors[j].isActive; + } + } + endProfile(" mixed"); + use(sum); + + delete [] actors; +} + +// A flat array of contiguous actors with the active ones first. +void testSorted() +{ + SmallActor* actors = new SmallActor[NUM_ACTORS]; + + // Enable half of them. + int numActive = NUM_ACTORS / 2; + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_FRAMES; i++) + { + for (int j = 0; j < numActive; j++) + { + sum += actors[j].update(); + } + + for (int j = 0; j < NUM_ACTORS; j++) + { + // Randomly change active state. + if (randRange(1, TOGGLE_RATE) == 0) + { + if (j < numActive) + { + // Inactive, so move it after all of the active ones. + numActive--; + } + else + { + // Active, so move it to the end of the active section. + numActive++; + } + SmallActor temp = actors[j]; + actors[j] = actors[numActive]; + actors[numActive] = temp; + } + } + } + endProfile("sorted"); + use(sum); + + delete [] actors; +} + +int main(int argc, const char * argv[]) +{ + srand((unsigned int)time(0)); + + for (int i = 0; i < 4; i++) + { + testMixed(); + testSorted(); + } + + return 0; +} + diff --git a/code/structure-of-arrays/combined/combined.xcodeproj/project.pbxproj b/code/structure-of-arrays/combined/combined.xcodeproj/project.pbxproj new file mode 100644 index 0000000..08c7416 --- /dev/null +++ b/code/structure-of-arrays/combined/combined.xcodeproj/project.pbxproj @@ -0,0 +1,222 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 292A45EC18397B9200C34813 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 292A45EB18397B9200C34813 /* main.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 292A45DD18397B7700C34813 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 292A45DF18397B7700C34813 /* combined */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = combined; sourceTree = BUILT_PRODUCTS_DIR; }; + 292A45EB18397B9200C34813 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 292A45ED18397B9700C34813 /* utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = utils.h; path = ../shared/utils.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 292A45DC18397B7700C34813 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 292A45D618397B7700C34813 = { + isa = PBXGroup; + children = ( + 292A45EB18397B9200C34813 /* main.cpp */, + 292A45ED18397B9700C34813 /* utils.h */, + 292A45E018397B7700C34813 /* Products */, + ); + sourceTree = ""; + }; + 292A45E018397B7700C34813 /* Products */ = { + isa = PBXGroup; + children = ( + 292A45DF18397B7700C34813 /* combined */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 292A45DE18397B7700C34813 /* combined */ = { + isa = PBXNativeTarget; + buildConfigurationList = 292A45E818397B7700C34813 /* Build configuration list for PBXNativeTarget "combined" */; + buildPhases = ( + 292A45DB18397B7700C34813 /* Sources */, + 292A45DC18397B7700C34813 /* Frameworks */, + 292A45DD18397B7700C34813 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = combined; + productName = combined; + productReference = 292A45DF18397B7700C34813 /* combined */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 292A45D718397B7700C34813 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Bob Nystrom"; + }; + buildConfigurationList = 292A45DA18397B7700C34813 /* Build configuration list for PBXProject "combined" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 292A45D618397B7700C34813; + productRefGroup = 292A45E018397B7700C34813 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 292A45DE18397B7700C34813 /* combined */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 292A45DB18397B7700C34813 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 292A45EC18397B9200C34813 /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 292A45E618397B7700C34813 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 292A45E718397B7700C34813 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 292A45E918397B7700C34813 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 292A45EA18397B7700C34813 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 292A45DA18397B7700C34813 /* Build configuration list for PBXProject "combined" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 292A45E618397B7700C34813 /* Debug */, + 292A45E718397B7700C34813 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 292A45E818397B7700C34813 /* Build configuration list for PBXNativeTarget "combined" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 292A45E918397B7700C34813 /* Debug */, + 292A45EA18397B7700C34813 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = 292A45D718397B7700C34813 /* Project object */; +} diff --git a/code/structure-of-arrays/combined/main.cpp b/code/structure-of-arrays/combined/main.cpp new file mode 100644 index 0000000..10a92df --- /dev/null +++ b/code/structure-of-arrays/combined/main.cpp @@ -0,0 +1,165 @@ +// +// main.cpp +// combined +// +// Created by Bob Nystrom on 11/17/13. +// Copyright (c) 2013 Bob Nystrom. All rights reserved. +// + +#include + +#include "utils.h" + +static const int NUM_FIELDS = 4; +static const int NUM_ACTORS = 2000; +static const int NUM_COMPONENTS = 4; +static const int NUM_FRAMES = 10000; +static const int NUM_COLD_FIELDS = 16; +static const int EXTRA = 10; + +class Component +{ +public: + int data[NUM_FIELDS]; + + Component() + { + for (int i = 0; i < NUM_FIELDS; i++) data[i] = i; + } + + int update() + { + int sum = 0; + for (int i = 0; i < NUM_FIELDS; i++) sum += data[i]; + return sum; + } +}; + +class ActorBefore +{ +public: + bool isActive = true; + + Component* components[NUM_COMPONENTS]; + int cold[NUM_COLD_FIELDS]; +}; + +void testSlow(float best) +{ + ActorBefore** actors = new ActorBefore*[NUM_ACTORS * EXTRA]; + + // Create a bunch of actors with random amounts of space between them. + for (int i = 0; i < NUM_ACTORS * EXTRA; i++) + { + actors[i] = new ActorBefore(); + } + + // Shuffle them in the list. + shuffle(actors, NUM_ACTORS * EXTRA); + + // Deactivate half of them. + for (int i = 0; i < NUM_ACTORS * EXTRA / 2; i++) + { + actors[i]->isActive = false; + } + + // Shuffle them in the list. + shuffle(actors, NUM_ACTORS * EXTRA); + + // Create a bunch of components. + Component** components = new Component*[NUM_ACTORS * NUM_COMPONENTS * EXTRA]; + for (int i = 0; i < NUM_ACTORS * NUM_COMPONENTS * EXTRA; i++) + { + components[i] = new Component(); + } + + // Shuffle them in the list. + shuffle(components, NUM_ACTORS * NUM_COMPONENTS * EXTRA); + + // Wire them up to actors. + for (int i = 0; i < NUM_ACTORS; i++) + { + for (int j = 0; j < NUM_COMPONENTS; j++) + { + actors[i]->components[j] = components[i * NUM_COMPONENTS + j]; + } + } + + // Run the simulation. + startProfile(); + int sum = 0; + for (int f = 0; f < NUM_FRAMES; f++) + { + for (int c = 0; c < NUM_COMPONENTS; c++) + { + for (int a = 0; a < NUM_ACTORS; a++) + { + if (!actors[a]->isActive) continue; + sum += actors[a]->components[c]->update(); + } + } + } + endProfile("worst", best); + if (sum != 240000000) printf("%d\n", sum); + + // Free everything. + for (int i = 0; i < NUM_ACTORS; i++) + { + delete actors[i]; + } + + for (int i = 0; i < NUM_ACTORS * NUM_COMPONENTS; i++) + { + delete components[i]; + } + + delete [] actors; + delete [] components; +} + +float testFast() +{ + Component* components[NUM_COMPONENTS]; + + // Create the component arrays. + for (int i = 0; i < NUM_COMPONENTS; i++) + { + components[i] = new Component[NUM_ACTORS]; + } + + // Run the simulation. + startProfile(); + int sum = 0; + for (int f = 0; f < NUM_FRAMES; f++) + { + for (int c = 0; c < NUM_COMPONENTS; c++) + { + Component* componentArray = components[c]; + for (int a = 0; a < NUM_ACTORS / 2; a++) + { + sum += componentArray[a].update(); + } + } + } + float result = endProfile("best"); + if (sum != 240000000) printf("%d\n", sum); + + // Free everything. + for (int i = 0; i < NUM_COMPONENTS; i++) + { + delete [] components[i]; + } + + return result; +} + +int main(int argc, const char * argv[]) +{ + for (int i = 0; i < 3; i++) + { + float best = testFast(); + testSlow(best); + } + return 0; +} + diff --git a/code/structure-of-arrays/component_update/component_update.xcodeproj/project.pbxproj b/code/structure-of-arrays/component_update/component_update.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ee7253e --- /dev/null +++ b/code/structure-of-arrays/component_update/component_update.xcodeproj/project.pbxproj @@ -0,0 +1,222 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 29234991182F06C500E5CC37 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29234990182F06C500E5CC37 /* main.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 29234982182F06A600E5CC37 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 29234984182F06A600E5CC37 /* component_update */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = component_update; sourceTree = BUILT_PRODUCTS_DIR; }; + 29234990182F06C500E5CC37 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 29234993182F083E00E5CC37 /* utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = utils.h; path = ../shared/utils.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 29234981182F06A600E5CC37 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2923497B182F06A600E5CC37 = { + isa = PBXGroup; + children = ( + 29234993182F083E00E5CC37 /* utils.h */, + 29234990182F06C500E5CC37 /* main.cpp */, + 29234985182F06A600E5CC37 /* Products */, + ); + sourceTree = ""; + }; + 29234985182F06A600E5CC37 /* Products */ = { + isa = PBXGroup; + children = ( + 29234984182F06A600E5CC37 /* component_update */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 29234983182F06A600E5CC37 /* component_update */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2923498D182F06A600E5CC37 /* Build configuration list for PBXNativeTarget "component_update" */; + buildPhases = ( + 29234980182F06A600E5CC37 /* Sources */, + 29234981182F06A600E5CC37 /* Frameworks */, + 29234982182F06A600E5CC37 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = component_update; + productName = component_update; + productReference = 29234984182F06A600E5CC37 /* component_update */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2923497C182F06A600E5CC37 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Bob Nystrom"; + }; + buildConfigurationList = 2923497F182F06A600E5CC37 /* Build configuration list for PBXProject "component_update" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 2923497B182F06A600E5CC37; + productRefGroup = 29234985182F06A600E5CC37 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 29234983182F06A600E5CC37 /* component_update */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 29234980182F06A600E5CC37 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29234991182F06C500E5CC37 /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2923498B182F06A600E5CC37 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 2923498C182F06A600E5CC37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 2923498E182F06A600E5CC37 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 2923498F182F06A600E5CC37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2923497F182F06A600E5CC37 /* Build configuration list for PBXProject "component_update" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2923498B182F06A600E5CC37 /* Debug */, + 2923498C182F06A600E5CC37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2923498D182F06A600E5CC37 /* Build configuration list for PBXNativeTarget "component_update" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2923498E182F06A600E5CC37 /* Debug */, + 2923498F182F06A600E5CC37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = 2923497C182F06A600E5CC37 /* Project object */; +} diff --git a/code/structure-of-arrays/component_update/main.cpp b/code/structure-of-arrays/component_update/main.cpp new file mode 100644 index 0000000..58a4f48 --- /dev/null +++ b/code/structure-of-arrays/component_update/main.cpp @@ -0,0 +1,299 @@ +#include "utils.h" + +// The tests a simulated game loop with a bunch of different memory +// configurations. You have a bunch of actors. Each actor has a few components. +// Each frame every component for every actor is updated. Except for "wrong +// order", all components of the same "type" are update for each actor. +// In other words, it updates component #1 for every actor, then component #2, +// etc. +// +// Each component has a few fields of state. "Updating" a component just does a +// minimal amount of work to read each state. +// +// The tests cover different ways the actors and components can be organized +// in memory to show cache effects. On my machine, output is something like: +// +// just components 9.4320ms +// wrong order 15.5460ms 1.65x +// inline actors 15.7990ms 1.68x +// order by actors 22.4630ms 2.38x +// order by components 13.0540ms 1.38x +// random components 89.5480ms 9.49x +// random actors 149.9980ms 15.90x +// +// This is with 4 components and 4 words of state for each component. Varying +// those numbers affects the timing. With 8 components and 1 word of state for +// each (a pathologically bad setup), I get numbers like: +// +// wrong order 7.8380ms 1.91x +// inline actors 17.7670ms 4.33x +// order by actors 44.5870ms 10.87x +// order by components 37.3180ms 9.10x +// random components 112.7880ms 27.51x +// random actors 221.1880ms 53.95x <-- !!! + +static const int NUM_ACTORS = 1000000; +static const int NUM_COMPONENTS = 4; +static const int NUM_FIELDS = 4; + +class Component +{ +public: + Component() + { + for (int i = 0; i < NUM_FIELDS; i++) data[i] = i; + } + + int data[NUM_FIELDS]; + + int update() + { + int sum = 0; + for (int i = 0; i < NUM_FIELDS; i++) sum += data[i]++; + return sum; + } +}; + +class InlineActor +{ +public: + Component components[NUM_COMPONENTS]; +}; + +class Actor +{ +public: + Component* components[NUM_COMPONENTS]; +}; + +// Iterate through the components in memory directly instead of going through +// actors. This is the best case scenario. +float testComponents() +{ + Component* components = new Component[NUM_ACTORS * NUM_COMPONENTS]; + + startProfile(); + long sum = 0; + for (int i = 0; i < NUM_ACTORS * NUM_COMPONENTS; i++) + { + sum += components[i].update(); + } + float elapsed = endProfile(" just components "); + + use(sum); + + delete [] components; + return elapsed; +} + +// For each actor, update all of its components. Not representative of real +// game since games update all components of one type. But gives us a point of +// comparison for testInline(). +void testWrongOrder(float best) +{ + InlineActor* actors = new InlineActor[NUM_ACTORS]; + + startProfile(); + long sum = 0; + for (int i = 0; i < NUM_ACTORS; i++) + { + for (int j = 0; j < NUM_COMPONENTS; j++) + { + sum += actors[i].components[j].update(); + } + } + endProfile(" wrong order ", best); + use(sum); + + delete [] actors; +} + +// For each component, update each actor, with components stored inline in the +// actor. +void testInline(float best) +{ + InlineActor* actors = new InlineActor[NUM_ACTORS]; + + startProfile(); + long sum = 0; + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + sum += actors[j].components[i].update(); + } + } + endProfile(" inline actors ", best); + use(sum); + + delete [] actors; +} + +// For each component, update each actor. The actor stores pointers to the +// components, and the components are ordered in memory by actor then component. +void testOrderedByActor(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + Component* components = new Component[NUM_ACTORS * NUM_COMPONENTS]; + + // Wire up the components to the actors. + for (int i = 0; i < NUM_ACTORS; i++) + { + for (int j = 0; j < NUM_COMPONENTS; j++) + { + actors[i].components[j] = &components[i * NUM_COMPONENTS + j]; + } + } + + startProfile(); + long sum = 0; + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + sum += actors[j].components[i]->update(); + } + } + endProfile(" order by actors ", best); + use(sum); + + delete [] actors; + delete [] components; +} + +// For each component, update each actor. The actor stores pointers to the +// components, and the components are ordered in memory by component then actor. +void testOrderedByComponent(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + Component* components = new Component[NUM_ACTORS * NUM_COMPONENTS]; + + // Wire up the components to the actors. + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + actors[j].components[i] = &components[i * NUM_ACTORS + j]; + } + } + + startProfile(); + long sum = 0; + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + sum += actors[j].components[i]->update(); + } + } + endProfile("order by components ", best); + use(sum); + + delete [] actors; + delete [] components; +} + +// For each component, update each actor. The actor stores pointers to the +// components, and the components are in random order. +void testRandomComponents(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + Component* components = new Component[NUM_ACTORS * NUM_COMPONENTS]; + + int* indexes = shuffledArray(NUM_ACTORS * NUM_COMPONENTS); + + // Wire up the components to the actors. + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + actors[j].components[i] = &components[indexes[i * NUM_ACTORS + j]]; + } + } + + delete [] indexes; + + startProfile(); + long sum = 0; + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + sum += actors[j].components[i]->update(); + } + } + endProfile(" random components ", best); + use(sum); + + delete [] actors; + delete [] components; +} + +// For each component, update each actor. The list of actors is pointers to +// actors that are in random order in memory. Each actor points to components +// that are in random order in memory. +// +// This is a realistic and also worst case. +void testRandomActors(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + Component* components = new Component[NUM_ACTORS * NUM_COMPONENTS]; + + int* componentIndexes = shuffledArray(NUM_ACTORS * NUM_COMPONENTS); + + // Wire up the components to the actors. + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + actors[j].components[i] = &components[componentIndexes[i * NUM_ACTORS + j]]; + } + } + + delete [] componentIndexes; + + // Fill the list of actors. + Actor** actorList = new Actor*[NUM_ACTORS]; + + int* actorIndexes = shuffledArray(NUM_ACTORS); + + for (int i = 0; i < NUM_ACTORS; i++) + { + actorList[i] = &actors[actorIndexes[i]]; + } + + delete [] actorIndexes; + + startProfile(); + long sum = 0; + for (int i = 0; i < NUM_COMPONENTS; i++) + { + for (int j = 0; j < NUM_ACTORS; j++) + { + sum += actorList[j]->components[i]->update(); + } + } + endProfile(" random actors ", best); + + use(sum); + + delete [] actors; + delete [] components; + delete [] actorList; +} + +int main(int argc, const char * argv[]) +{ + for (int i = 0; i < 4; i++) + { + float components = testComponents(); + testWrongOrder(components); + testInline(components); + testOrderedByActor(components); + testOrderedByComponent(components); + testRandomComponents(components); + testRandomActors(components); + } + + return 0; +} diff --git a/code/structure-of-arrays/is_active/is_active.xcodeproj/project.pbxproj b/code/structure-of-arrays/is_active/is_active.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1ea3eef --- /dev/null +++ b/code/structure-of-arrays/is_active/is_active.xcodeproj/project.pbxproj @@ -0,0 +1,222 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 292349AB182F19CC00E5CC37 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 292349AA182F19CC00E5CC37 /* main.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2923499C182F19B800E5CC37 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2923499E182F19B800E5CC37 /* is_active */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = is_active; sourceTree = BUILT_PRODUCTS_DIR; }; + 292349AA182F19CC00E5CC37 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 292349AC182F19D200E5CC37 /* utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = utils.h; path = ../shared/utils.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2923499B182F19B800E5CC37 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29234995182F19B800E5CC37 = { + isa = PBXGroup; + children = ( + 292349AC182F19D200E5CC37 /* utils.h */, + 292349AA182F19CC00E5CC37 /* main.cpp */, + 2923499F182F19B800E5CC37 /* Products */, + ); + sourceTree = ""; + }; + 2923499F182F19B800E5CC37 /* Products */ = { + isa = PBXGroup; + children = ( + 2923499E182F19B800E5CC37 /* is_active */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2923499D182F19B800E5CC37 /* is_active */ = { + isa = PBXNativeTarget; + buildConfigurationList = 292349A7182F19B800E5CC37 /* Build configuration list for PBXNativeTarget "is_active" */; + buildPhases = ( + 2923499A182F19B800E5CC37 /* Sources */, + 2923499B182F19B800E5CC37 /* Frameworks */, + 2923499C182F19B800E5CC37 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = is_active; + productName = is_active; + productReference = 2923499E182F19B800E5CC37 /* is_active */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29234996182F19B800E5CC37 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Bob Nystrom"; + }; + buildConfigurationList = 29234999182F19B800E5CC37 /* Build configuration list for PBXProject "is_active" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 29234995182F19B800E5CC37; + productRefGroup = 2923499F182F19B800E5CC37 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2923499D182F19B800E5CC37 /* is_active */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 2923499A182F19B800E5CC37 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 292349AB182F19CC00E5CC37 /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 292349A5182F19B800E5CC37 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 292349A6182F19B800E5CC37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 292349A8182F19B800E5CC37 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 292349A9182F19B800E5CC37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 29234999182F19B800E5CC37 /* Build configuration list for PBXProject "is_active" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 292349A5182F19B800E5CC37 /* Debug */, + 292349A6182F19B800E5CC37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 292349A7182F19B800E5CC37 /* Build configuration list for PBXNativeTarget "is_active" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 292349A8182F19B800E5CC37 /* Debug */, + 292349A9182F19B800E5CC37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29234996182F19B800E5CC37 /* Project object */; +} diff --git a/code/structure-of-arrays/is_active/main.cpp b/code/structure-of-arrays/is_active/main.cpp new file mode 100644 index 0000000..00382f3 --- /dev/null +++ b/code/structure-of-arrays/is_active/main.cpp @@ -0,0 +1,334 @@ +#include "utils.h" + +static const int NUM_ACTORS = 10000000; +static const int NUM_FIELDS = 4; + +// When creating the contiguous array of actors, we'll multiply the number of +// actors by this to add some extra padding and spread them out in memory more. +static const int DEAD_ACTORS = 5; + +class Actor +{ +public: + Actor() + { + for (int i = 0; i < NUM_FIELDS; i++) data[i] = i; + } + + bool isActive = true; + + int data[NUM_FIELDS]; + + int update() + { + int sum = 0; + for (int i = 0; i < NUM_FIELDS; i++) sum += data[i]; + return sum; + } +}; + +// A flat array of contiguous actors with the active ones first in the list. +// We know how many are active, so don't have to check at all. +float testInOrderInlineKnown() +{ + Actor* actors = new Actor[NUM_ACTORS]; + + // De-activate half of them. + for (int i = NUM_ACTORS / 2; i < NUM_ACTORS; i++) + { + actors[i].isActive = false; + } + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_ACTORS / 2; i++) + { + sum += actors[i].update(); + } + float elapsed = endProfile(" best case"); + use(sum); + + delete [] actors; + return elapsed; +} + +// A flat array of contiguous actors with the active ones first in the list. +// We stop as soon as we hit an inactive one since we know they're sorted. +void testInOrderBailInline(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + + // De-activate half of them. + for (int i = NUM_ACTORS / 2; i < NUM_ACTORS; i++) + { + actors[i].isActive = false; + } + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_ACTORS; i++) + { + if (!actors[i].isActive) break; + sum += actors[i].update(); + } + endProfile(" in order bail", best); + use(sum); + + delete [] actors; +} + +// A flat array of contiguous actors with the active ones first in the list. +void testInOrderInline(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + + // De-activate half of them. + for (int i = 0; i < NUM_ACTORS / 2; i++) + { + actors[i].isActive = false; + } + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_ACTORS; i++) + { + if (actors[i].isActive) + { + sum += actors[i].update(); + } + } + endProfile(" in order inline", best); + use(sum); + + delete [] actors; +} + +// A flat array of contiguous actors with the active and inactive ones randomly +// distributed. +void testMixedInline(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + + // De-activate half of them. + int* indexes = shuffledArray(NUM_ACTORS); + for (int i = 0; i < NUM_ACTORS / 2; i++) + { + actors[indexes[i]].isActive = false; + } + delete [] indexes; + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_ACTORS; i++) + { + if (actors[i].isActive) + { + sum += actors[i].update(); + } + } + endProfile(" mixed inline", best); + use(sum); + + delete [] actors; +} + +// A flat array of contiguous actors with the active and inactive ones randomly +// distributed, then sorted during the profile. We stop as soon as we hit an +// inactive one. +void testSortedInline(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + + // De-activate half of them. + int* indexes = shuffledArray(NUM_ACTORS); + for (int i = 0; i < NUM_ACTORS / 2; i++) + { + actors[indexes[i]].isActive = false; + } + delete [] indexes; + + long sum = 0; + startProfile(); + + // Sort the active ones to the front. + int search = 1; + int numActive = 0; + for (; numActive < NUM_ACTORS; numActive++) + { + if (actors[numActive].isActive) continue; + + // Find an active actor to swap with this one. + for (; search < NUM_ACTORS; search++) + { + if (actors[search].isActive) + { + Actor temp = actors[numActive]; + actors[numActive] = actors[search]; + actors[search] = temp; + search++; + break; + } + } + + // If we searched past the end, we've moved all of the active ones + // forward. + if (search >= NUM_ACTORS) break; + } + + for (int i = 0; i < numActive; i++) + { + sum += actors[i].update(); + } + endProfile(" sorted inline", best); + use(sum); + + delete [] actors; +} + +// An array of pointers to randomly ordered actors, with the active first in +// the list. Bail out as soon as we get to an inactive one. +void testInOrderPointer(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + + Actor** actorList = new Actor*[NUM_ACTORS]; + int* indexes = shuffledArray(NUM_ACTORS); + for (int i = 0; i < NUM_ACTORS; i++) + { + actorList[i] = &actors[indexes[i]]; + actorList[i]->isActive = i < NUM_ACTORS / 2; + } + indexes = shuffledArray(NUM_ACTORS); + delete [] indexes; + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_ACTORS; i++) + { + if (!actorList[i]->isActive) break; + sum += actorList[i]->update(); + } + endProfile("in order pointer", best); + use(sum); + + delete [] actors; +} + +// An array of pointers to randomly ordered actors, with the active and +// inactive ones randomly distributed. +void testMixedPointer(float best) +{ + Actor* actors = new Actor[NUM_ACTORS * DEAD_ACTORS]; + + // De-activate half of them. + int* indexes = shuffledArray(NUM_ACTORS * DEAD_ACTORS); + for (int i = 0; i < NUM_ACTORS * DEAD_ACTORS / 2; i++) + { + actors[indexes[i]].isActive = false; + } + delete [] indexes; + + Actor** actorList = new Actor*[NUM_ACTORS]; + indexes = shuffledArray(NUM_ACTORS * DEAD_ACTORS); + for (int i = 0; i < NUM_ACTORS; i++) + { + actorList[i] = &actors[indexes[i]]; + } + delete [] indexes; + + long sum = 0; + startProfile(); + for (int i = 0; i < NUM_ACTORS; i++) + { + if (!actorList[i]->isActive) continue; + sum += actorList[i]->update(); + } + endProfile(" mixed pointer", best); + use(sum); + + delete [] actors; +} + +// An array of pointers to randomly ordered actors, with the active and +// inactive ones randomly distributed. They are sorted during the profile. +// Since we determine the number of active actors during the sort, we skip the +// active check. +void testSortedPointer(float best) +{ + Actor* actors = new Actor[NUM_ACTORS]; + + // De-activate half of them. + int* indexes = shuffledArray(NUM_ACTORS); + for (int i = 0; i < NUM_ACTORS / 2; i++) + { + actors[indexes[i]].isActive = false; + } + delete [] indexes; + + Actor** actorList = new Actor*[NUM_ACTORS]; + for (int i = 0; i < NUM_ACTORS; i++) + { + actorList[i] = &actors[indexes[i]]; + } + indexes = shuffledArray(NUM_ACTORS); + delete [] indexes; + + long sum = 0; + startProfile(); + + // Sort the active ones to the front. + int search = 1; + int numActive = 0; + for (; numActive < NUM_ACTORS; numActive++) + { + if (actorList[numActive]->isActive) continue; + + // Find an active actor to swap with this one. + for (; search < NUM_ACTORS; search++) + { + if (actorList[search]->isActive) + { + Actor* temp = actorList[numActive]; + actorList[numActive] = actorList[search]; + actorList[search] = temp; + search++; + break; + } + } + + // If we searched past the end, we've moved all of the active ones + // forward. + if (search >= NUM_ACTORS) break; + } + + for (int i = 0; i < numActive; i++) + { + sum += actorList[i]->update(); + } + endProfile(" sorted pointer", best); + use(sum); + + delete [] actors; +} + +// TODO(bob): Do similar test but with virtual method calls? + +int main(int argc, const char * argv[]) +{ + srand((unsigned int)time(0)); + + for (int i = 0; i < 4; i++) + { + float elapsed = testInOrderInlineKnown(); + testInOrderBailInline(elapsed); + testInOrderInline(elapsed); + testMixedInline(elapsed); + testSortedInline(elapsed); + testInOrderPointer(elapsed); + testMixedPointer(elapsed); + testSortedPointer(elapsed); + } + + return 0; +} + diff --git a/code/structure-of-arrays/random_array_access/main.cpp b/code/structure-of-arrays/random_array_access/main.cpp new file mode 100644 index 0000000..3abc2c8 --- /dev/null +++ b/code/structure-of-arrays/random_array_access/main.cpp @@ -0,0 +1,81 @@ +#include + +#include "utils.h" + +// Based on http://stackoverflow.com/questions/1126529/what-is-the-cost-of-an-l1-cache-miss +// +// Tries to show effects of cache misses by modifying elements of an array +// twice, once in order and once in random order. +// +// On my machine, I get results like: +// ordered: 43.9250ms +// random: 308.4460ms +// 7.02x +// ordered: 14.6060ms +// random: 314.2920ms +// 21.52x +// ordered: 14.3640ms +// random: 304.5980ms +// 21.21x +// ordered: 14.3160ms +// random: 316.0700ms +// 22.08x +// ordered: 15.6090ms +// random: 317.1620ms +// 20.32x +// ordered: 15.7320ms +// random: 281.0440ms +// 17.86x + +void accessRandomArray(int iterations, int length) +{ + srand((unsigned int)time(0)); + + // Allocate the data as one single chunk of memory. + int* nodes = new int[length]; + + // Create array that give the access order for the data. Going through the + // indirect for both the ordered and random traversals ensures the randomness + // itself doesn't add any overhead. + int* orderedIndexes = new int[length]; + for (int i = 0; i < length; i++) + { + orderedIndexes[i] = i; + } + + int* randomIndexes = shuffledArray(length); + + long sum = 0; + + // Run the whole thing a few times to make sure the numbers are stable. + for (int a = 0; a < iterations; a++) + { + // Write the data in order. + startProfile(); + for (int i = 0; i < length; i++) nodes[orderedIndexes[i]] = i; + float ordered = endProfile("ordered: "); + + // Actually use the data so the compiler doesn't optimize it to nothing. + for (int i = 0; i < iterations; i++) sum += nodes[i]; + + // Now access the array positions in a "random" order. + startProfile(); + for (int i = 0; i < length; i++) nodes[randomIndexes[i]] = i; + endProfile(" random: ", ordered); + + // Actually use the data so the compiler doesn't optimize it to nothing. + for (int i = 0; i < iterations; i++) sum -= nodes[i]; + } + + // Actually use the data so the compiler doesn't optimize it to nothing. + if (sum != 0) printf("Got wrong sum %ld (should be 0)\n", sum); + + delete [] nodes; + delete [] orderedIndexes; + delete [] randomIndexes; +} + +int main( int argc, char* argv[] ) +{ + accessRandomArray(6, 20000000); +} \ No newline at end of file diff --git a/code/structure-of-arrays/random_array_access/random_array_access.xcodeproj/project.pbxproj b/code/structure-of-arrays/random_array_access/random_array_access.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e9c8baf --- /dev/null +++ b/code/structure-of-arrays/random_array_access/random_array_access.xcodeproj/project.pbxproj @@ -0,0 +1,223 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 2923497A182F068E00E5CC37 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29234979182F068E00E5CC37 /* main.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2923496A182F018B00E5CC37 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2923496C182F018B00E5CC37 /* random_array_access */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = random_array_access; sourceTree = BUILT_PRODUCTS_DIR; }; + 29234979182F068E00E5CC37 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + 29234994182F086F00E5CC37 /* utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = utils.h; path = ../shared/utils.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 29234969182F018B00E5CC37 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29234963182F018B00E5CC37 = { + isa = PBXGroup; + children = ( + 29234994182F086F00E5CC37 /* utils.h */, + 29234979182F068E00E5CC37 /* main.cpp */, + 2923496D182F018B00E5CC37 /* Products */, + ); + sourceTree = ""; + }; + 2923496D182F018B00E5CC37 /* Products */ = { + isa = PBXGroup; + children = ( + 2923496C182F018B00E5CC37 /* random_array_access */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2923496B182F018B00E5CC37 /* random_array_access */ = { + isa = PBXNativeTarget; + buildConfigurationList = 29234975182F018B00E5CC37 /* Build configuration list for PBXNativeTarget "random_array_access" */; + buildPhases = ( + 29234968182F018B00E5CC37 /* Sources */, + 29234969182F018B00E5CC37 /* Frameworks */, + 2923496A182F018B00E5CC37 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = random_array_access; + productName = random_array_access; + productReference = 2923496C182F018B00E5CC37 /* random_array_access */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29234964182F018B00E5CC37 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0500; + ORGANIZATIONNAME = "Bob Nystrom"; + }; + buildConfigurationList = 29234967182F018B00E5CC37 /* Build configuration list for PBXProject "random_array_access" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 29234963182F018B00E5CC37; + productRefGroup = 2923496D182F018B00E5CC37 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2923496B182F018B00E5CC37 /* random_array_access */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 29234968182F018B00E5CC37 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2923497A182F068E00E5CC37 /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 29234973182F018B00E5CC37 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 29234974182F018B00E5CC37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 29234976182F018B00E5CC37 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 29234977182F018B00E5CC37 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 29234967182F018B00E5CC37 /* Build configuration list for PBXProject "random_array_access" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29234973182F018B00E5CC37 /* Debug */, + 29234974182F018B00E5CC37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 29234975182F018B00E5CC37 /* Build configuration list for PBXNativeTarget "random_array_access" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29234976182F018B00E5CC37 /* Debug */, + 29234977182F018B00E5CC37 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29234964182F018B00E5CC37 /* Project object */; +} diff --git a/code/structure-of-arrays/shared/utils.h b/code/structure-of-arrays/shared/utils.h new file mode 100644 index 0000000..d6c2bc4 --- /dev/null +++ b/code/structure-of-arrays/shared/utils.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include + +clock_t startTime; + +void startProfile() +{ + startTime = clock(); +} + +float endProfile(const char* message) +{ + float elapsed = (float)(clock() - startTime) * 1000.0f / CLOCKS_PER_SEC; + printf("%s%10.4fms\n", message, elapsed); + return elapsed; +} + +float endProfile(const char* message, float comparison) +{ + float elapsed = (float)(clock() - startTime) * 1000.0f / CLOCKS_PER_SEC; + printf("%s%10.4fms %6.2fx\n", message, elapsed, elapsed / comparison); + return elapsed; +} + +// Get random value in the given range (half-inclusive). +int randRange(int m, int n) +{ + return rand() % (n - m) + m; +} + +// Make sure the code leading to the argument to this call isn't compiled out. +void use(long sum) +{ + if (sum == 123) printf("!"); +} + +template +T median(std::vector items) +{ + std::sort(items.begin(), items.end()); + if (items.size() % 2 == 0) + { + return (items[items.size() / 2] + items[items.size() / 2 + 1]) / 2; + } + else + { + return items[items.size() / 2]; + } +} + +int* shuffledArray(int length) +{ + int* array = new int[length]; + for (int i = 0; i < length; i++) array[i] = i; + + for (int i = 0; i < length; i++) + { + int j = randRange(i, length); + int t = array[i]; + array[i] = array[j]; + array[j] = t; + } + + return array; +} + +template +void shuffle(T* array, int length) +{ + for (int i = 0; i < length; i++) + { + int j = randRange(i, length); + T temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/draft/first draft/game-loop.markdown b/draft/first draft/game-loop.markdown new file mode 100644 index 0000000..292aa3b --- /dev/null +++ b/draft/first draft/game-loop.markdown @@ -0,0 +1,517 @@ +^title Game Loop +^section Sequencing Patterns + +## Intent + +*Simulate the smooth progression of time independently of user input +and processor speed.* + +## Motivation + +If there is one pattern this book couldn't possibly live without, it's this one. When you think "patterns" and "game programming", the game loop is the quintessential example. Almost every game has one, no two are exactly alike, and relatively few programs outside of games use them. + + + +To see what they're useful for, let's take a quick trip down memory lane. In the olden days of computer programming when everyone had beards, programs worked like your washing machine: you put a program in, you got results out. Done. These were *batch mode* programs: once the work was done, the program stopped. + +You still see these today, though we thankfully don't have to write them on punch cards any more. Shell scripts, command line programs, and even the little Python script that turns a pile of Markdown into this book are all batch mode programs. + +### Let's Talk + +But not too long after that, programmers realized they were lonely and wanted to have live conversations with their machines. *Interactive* programs were born. Some of the first interactive programs were games: + + + + YOU ARE STANDING AT THE END OF A ROAD BEFORE A SMALL BRICK + BUILDING . AROUND YOU IS A FOREST. A SMALL + STREAM FLOWS OUT OF THE BUILDING AND DOWN A GULLY. + + > GO IN + YOU ARE INSIDE A BUILDING, A WELL HOUSE FOR A LARGE SPRING. + + + +Early interactive programs worked like a dialogue. The program would say something. Then it would patiently wait for your command. Then it would respond. Back and forth. When it was your turn to give it a command, it sat there with infinite patience doing pretty much nothing. Something like: + + + +^code 1 + +Compared to batch mode programs, the `while` loop is the big difference here. That's what let programs keep running as long as the user wanted. Now we have something like a primordial "game loop". But things changed a lot when graphic user interfaces came onto the scene shortly after the PC revolution. + +### Event Loops + +Most applications aren't batch mode any more. A word processor would be pretty pointless if it just exited immediately every time you ran it. So the loop must still be there somewhere, but if you're an application programmer, you rarely see it. Instead, it's handled by the operating system or GUI framework. Somewhere deep in their bowels is something like: + + +^code 2 + +These "events" will be user interactions: a button was clicked, the mouse was moved, a key was pressed, you get the idea. In your application code, you wire up *event handlers*. You specify chunks of code that should run when an event occurs. + + + +If you look though, that loop is pretty much like the old text adventure game loop. It still *blocks* waiting for user input. Your favorite word processor spends most of its time just sitting idle, waiting for you to click or type something. + +You *can* write games this way, at least very simple ones. Think Minesweeper or solitaire. But once you start wanting animation, sound and all of the other fun stuff that makes a game a game, that's not going to fly anymore. + +### A Living Breathing World + +The key difference between games and most other software here is that most games keep doing stuff even when the user isn't providing input. If you just sit staring at the screen, the game doesn't grind to a halt. Animations keep animating. Visual effects dance and sparkle. The music keeps playing. + +**TODO: Finish rolling this into this section:** + +This is the first key part of a real game loop: it *processes* user input, but it doesn't *wait* for it. The loop always keeps spinning. Event-based code has a *push*-based model for handling user input. For example, if the game character jumps when you press a button, the logic looks like: + +1. The OS detects a button press. +2. It calls the event dispatcher. +3. That calls the event handler we've wired up. +4. We make the main character jump. + +This has a few problems. First, this could happen at any time. Maybe you're in the middle of rendering the frame or processing enemy AI. This could let the player act out of turn. Second, you don't have any control over the *rate* of these events. If you aren't careful, the player could get more moves in than the enemies just by flooding the game with events. + +To avoid this, in games user input is usually processed using a pull model: + +1. The game updates all of the entities. +2. When it gets to a player-controlled entity, we see if the button is + currently pressed. +3. If it is, we make the main character jump. + +The difference here is that we respond to the input when it's most convenient for *us*, not when the OS starts yelling at us. + +**TODO: Make a segue here.** + +### A World Out of Time + + + +Our game now has a loop that will just keep spinning on its own. Great. But that leads to the obvious question: how *fast* does it loop? Each turn through the game loop, you'll be updating the state of the game world by some amount. From the perspective of an inhabitant of the game world, the clock has sounded one tiny "tick" and they've moved one tiny unit into the future. Game time has advanced. + +The game world has its own internal clock, but the *player* also has a sense of time. *Real* time, with clocks and seconds and calendars and sundials. If you think of the turning of the game loop as one axle and the turning of the human player's real clock hands as another, there's some set of gears between them. There's a conversion between game time and real time. + +This rate, say "frames per second" determines how fast the game runs. When it's slow, game characters slog through molasses. When it's fast, it turns into a Buster Keaton movie. + +There are two gears between the real time and game time clocks that control that rate. The first is *how much work has to be done each frame*. If you've got complex physics, a bunch of game objects, and lots of graphic detail, all of that is going to keep your CPU and GPU busy for a while, and it will take that much longer for the frame to complete. You control this by deciding how complex your game is. + +The other gear is *how fast the hardware itself runs.* Faster chips can churn through more work in the same amount of time. Multiple cores, GPUs, dedicated audio hardware, and the OS's scheduler all effect how much you can get done in one tick. + +### Seconds Per Second + +Early on in video games, that second gear was actually fixed. If you were writing a game for the NES or Apple IIe, you knew *exactly* what CPU your game was running on and you could (and did) code specifically for that. That meant you only had one gear to worry about: how much work you did each tick. + + + +Early videogames were carefully coded to do just enough work each frame so that the game played at the speed that the developers wanted. But if you tried to play that same game on a faster or slower machine, then all of the sudden the game would speed up or slow down. + +These days, though, almost no developer has the luxury of knowing exactly what hardware their game will run on. Instead, the game has to intelligently adapt to a wide variety of hardware. This is the other key job of your game loop: it keeps the game playing at a consistent speed despite differences in the underlying hardware. + +## The Pattern + +A **game loop** runs continuously until the game is exited. Each turn of the +loop, it **processes user input** without blocking, and **updates the game +state**. It keeps track of the passage of time so that it can **control the +rate of gameplay**. + +## When to Use It + +Using a pattern when it isn't neeeded is often as bad as not using a pattern where it is, so this section is usually useful as a guard against over-enthusiasm. Every game doesn't need every pattern under the sun. + + + +But this pattern is a bit different. I can say with pretty good confidence that you probably *will* use this pattern. If you're using a game engine, you probably won't write it yourself, but it's still there. + +If you're making a turn-based game, you may think you won't need a full game loop. Instead you can rely on the OS's event loop. You may get away with this for very simple games. But in most turn-based games, even though the *game state* doesn't advance without user input, the *visual* and *audible* state of the game usually does. Idle animation and music keep running even when it's "waiting" for you to take your turn. + +In other words, unless you're: + +* making a simple turn-based game without animation +* using an engine that does it for you + +then odds are you'll have a game loop. + +## Keep in Mind + +### This is the core loop of your game + + + +Seems obvious, I know, but code in the game loop will be running constantly. They say a program spends 90% of its time in 10% of the code. Your game loop and the stuff it calls directly will be in that 10%. Think carefully about performance and make sure you profile when you make changes to your core loop. + +### You may need to coordinate with the platform's event loop + +If you're building your game on top of an OS or platform that has a graphic UI and event loop built in, then you have *two* application loops in play. You'll need to coordinate them. + +Sometimes, you can just take control and make your loop the main one. For example, if you're writing a game against the venerable Windows API, your `main()` can just have a game loop. Inside, you can call `PeekMessage()` to also handle and dispatch events from the OS. Unlike `GetMessage()`, `PeekMessage()` doesn't block waiting for user input so your game loop will keep cranking. + +Other platforms don't let you opt out of the event loop as easily. If you're writing a game that runs in a web browser, the event loop is deeply built into browser's execution model. There, the event loop will be your one loop and you'll use it as your game loop too. You'll call something like `requestAnimationFrame()` to let the event loop call your every tick. + +### Think about the *range* of handware your game runs on + +**TODO: Move to design decisions?** + +As we'll see below, you can code a game loop that adapts to different hardware speeds, but it can only adapt so far. If your game needs to run on an 8MHz 8086, no amount of clever coding in your game loop is going to get a detailed 3D fluid simulation running in realtime. + + + +The design of your game itself will need to take into account the range of hardware you plan to run on. This will affect all aspects of the design, everything from how detailed your graphics are, to how the physics work, to how big levels are and how many characters there are on them. + +You'll need to think about it coming from both directions too. You'll have to answer both "how does my game run on the worst hardware?" *and* "how does it run on the best?" If you only worry about the first, you'll end up with a game that looks cheap and unimpressive on powerful gear. If you only worry about the second, users with slower machines may find the game unusable. + +This chapter will just be about the game loop itself and how it adapts to the game's running speed, but there are also often other places in the game that can and will need to adapt to the hardware. Things like lowering graphic detail, reducing the number of visual effects, etc. all help you get your game on as many devices as possible, but are out of scope here. + +### You may need to think about battery life + +This section wouldn't have been here five years ago. Games ran on things +plugged into walls. But with the advent of smart phones, laptops, mobile gaming, the odds are good you do care about this now. A game that runs beautifully but turns your phone into a space heater that runs out of power in thirty minutes is not a game that makes people happy. + +Now you may need to think about making your game look great, but also use as little CPU as possible. There will likely be an *upper* bound to performance where you let the CPU sleep if you've done all the work you need to do in a frame. + +On the other hand, if you're making a console or PC game, you often want to cram +as much scale and cinematic drama into your game as you can. An idle CPU means +a wasted opportunity to make a game bigger, better, and more exciting than the +competition. + +## Sample Code + +For all this setup, the code for a game loop is actually pretty straightforward. We'll walk through a couple of different variations and go over their good and bad points. Since the point of a game loop is just to handle timing, the sample code here will call into a bunch of over fictitious methods that aren't presented here. Just imagine your engine's actual functionality there. + +### Run Run As Fast You Can + +Let's start with the simplest possible game loop: + +^code 3 + +This is what your loop looked like if you were writing a Commodore 64 game, but it's not too far removed from modern game loops. Three key pieces are there already. + +First, it polls user input without blocking. Then it calls `update()`. This advances the game simulation one step. It runs AI and physics (usually in that order) and handles any player-controlled characters. We do this *after* `pollInput()` so that we can have the game respond to user input in the same frame in which it was received. Finally, we call `render()` to draw the game and show the new game state to the user. + +This wins the simplicity battle, but that's about all it wins. The main problem here is you have little control over how fast the game runs. On a fast machine, that loop will spin so fast users won't be able to see what's going on. On a slow machine, the game will crawl. If you have a part of the game that's content-heavy or does more AI or physics, the game will actually play slower there. + +I'm showing you this code just to give you some context, but this is rarely what you want. + +### Draw and Wait + +There's a simple fix for this problem: + + 1. Store the current real-world time. + + 2. Do all of your work. + + 3. Wait until it's time for the next frame. + + + +Say you want your game to run at 60 FPS. That gives you about 16 milliseconds per frame. As long as you can reliably do all of your game processing and rendering in less than that time, you can run at a steady frame rate. It looks a bit like this: + +^code 4 + +The `sleep()` here will make sure the game doesn't run too *fast* if it takes less time to process the frame. That solves half the problem of the previous "run as fast as you can" loop. It's also a bit more battery-friendly, since it gives control back to the OS every time it calls `sleep()`. + +What it *doesn't* help with your game running too *slowly*. If it takes longer than 16 ms to update and render the game, your sleep time will be *negative*. If we had computers that could travel back in time, lots of things would be easier, but we don't. + +**TODO: talk about spiral of death** + +Instead, the game will just start to slow down. A crude fix is to just do less work each frame: cut down on the graphics and razzle dazzle, or dumb down the AI. But that impacts the quality of gameplay for all users, even ones on fast machines. If you're making a relatively simple game, you may not have this problem, and the above loop might be fine. Otherwise, we'll have to do something a bit more complex... + +### Fluid Time Step + +Let's try something a bit more sophisticated. The problem we have basically boils down do: + + 1. Each frame, we update the game by a fixed amount of time. + + 2. It takes a certain amount of real time to do that update. + + 3. If step two takes longer than step one, the game slows down. + +One obvious fix then, is to not fix the game time step when we update. If it takes longer than 16 ms to process a game frame, then the game can't keep up it if only advances 16 ms of *game* time each frame. + +But if each update advances a larger chunk of game time, we can update the game less frequently and it will still keep up. They call this a *variable* or *fluid* time step. It looks like: + +^code 5 + +Each frame, we keep determine how much *real* time passed since the last game update (`elapsed`). When we update the game state, we pass that in. The engine is then responsible for advancing the game world forward by that amount of time. + +For example, if a bullet is shooting across the screen, instead of moving it by a fixed distance, the engine will scale that by the elapsed time. As the frame rate goes down, the game engine moves things in larger increments to compensate. That bullet will get across the screen in the *same* amount of *real* time, even if it's ten or twenty ticks of game time. + +On its surface, it seems like this accomplishes our goals: + + * The game plays at a consistent rate on different hardware. + * Players with faster machines are rewarded with smoother gameplay. + +This is better, but still not good. The main problem is that making each frame of game time based on the system clock makes the game engine unpredictable, non-deterministic, and often leads to really nasty bugs. + +Here's a couple of examples of what can go wrong. Let's say we've got a two-player networked game and Fred has some beast of a gaming machine while George is using his grandmother's antique PC. We've got this little bullet flying across both of their screens. On Fred's machine, the game is running super fast, so each time step is tiny. We cram like 50 game ticks in the second it takes the bullet to cross the screen. Poor George can only fit about 5 frames in. + +This means the physics engine is going to update the bullet's position 50 times for Fred but only 5 times for George. Most games are using floating point numbers for this, and those numbers are *approximate* and subject to rounding error. Each time you add two floating point numbers, the answer you get back can be a bit off. Fred's machine is doing ten times as many operations, so he'll accumulate a bigger error than George. This means *their bullets will end up in different places*. Ouch! + +It gets worse. On George's machine, the bullet only has five frames to get across the screen, so each frame it's moving a pretty large distance. If a wall is narrow enough, it could be on one side in one frame and all the way through it on the next. On Fred's machine, the bullet will stop at the wall, but on George's, it passes right through. + + + +That's a particularly obvious examples of physics going haywire, but more subtle problems will crop of too if you have a variable time step. Game physics is a delicate art of emulating reality within the confines of a system that uses approximations for efficiency. These systems are carefully tuned and damped to avoid blowing up (think objects literally launching themselves in the air with completely crazy velocities) when errors in the approximations make the underlying equations goes sideways. That tuning is based in part on how much time it expects to elapse each frame. Change that, and your careful damping can fail to keep the system stable. + +I'm harping on physics here, but variable time steps are complex for other aspects of the game engine. Imagine writing AI code for an enemy aiming a gun and trying to take into account how far ahead of a moving target it needs to aim when you aren't even sure what point in time it will be in the next frame. + +The one part of the engine that usually isn't affected much by this is rendering. Since the rendering engine (more or less) just captures an instant in time, it usually doesn't care too much about how much time has advanced each frame. As long as the physics and AI systems put everything in the right place, it will render them wherever they are now. + +### Decouple Update and Render Rate + +We can use this fact to our advantage. We'll update the game using a fixed time step because that makes everything simpler and more stable for physics and AI. + +Think about it like this: At the beginning of the game loop, a certain amount of real time has elapsed since the last turn. This is how much game time we need to simulate in order to for the game's "now" to catch up with the user's "now". Once it's caught up, we render and show it to the user. The code looks a bit like: + +^code 6 + +There's a few pieces here: + +^code 7 + +At the beginning of each frame, we calculate how much real time passed. We add this to `lag`. That measures how far behind in time the game is compared to the real world. + +^code 8 + +We keep updating the game one fixed chunk at a time until we're as close to being caught up as we can be. Note that the time step here isn't the *visible* frame rate any more. `MS_PER_UPDATE` is just the *granularity* we use to update the game. We can render both faster or slower than that. + +You'll tune this for your game. The shorter this step is, the more processing time it will take to catch up to real time. The longer it is, the choppier the gameplay is. Ideally, you want it pretty short, often faster than 60 FPS, so that the game simulates with high fidelity on fast machines. + +But be careful not to make it *too* short. You need to make sure the time step is greater than the time it takes to process an `update()`, even on the slowest hardware. Otherwise, your game simply can't catch up no matter how hard it tries. + +Fortunately, this is usually pretty easy. The trick is that we've yanked *rendering* out of that loop. That frees up a bunch of CPU time. + +Once the game's time is caught up to the player's, we finally ready to render it. + +^code 9 + +Then we start it all over again. This is certainly more complex, but it's a big improvement over out simpler loops. We've got a fixed time step which makes the game state deterministic and predictable. The visible framerate goes up on fast machines, but on slow machines the game still plays at the right speed. + +### Interpolated Rendering + +There's one issue we're left with, and that's residual lag. We update the game at a fixed time step, but we *render* at arbitrary points in time. This means that from the user's perspective, the game will often display at a point in time in the between two updates. + +Here's a timeline: + + update update update update update update update update + | | | | | | | | + ------------------------------------------------------------------------> + | | | | | + render render render render render + + +As you can see, we update at a nice tight fixed interval. Meanwhile, we render +whenever we can. It's less frequent than updating, and isn't steady either. +Both of those are OK. The nasty part is that we don't always render right at the +point of updating. Look at the third render time. It's right between two updates: + + update update + | | + ...---------------- + | + render + +Imagine a bullet is flying across the screen. On the first update, it's on the left side. The second update moves it to the right side. The game is rendered at a point in time right between those two updates, so the user will expect to see that bullet right in the middle of the screen. With our current implementation, it will still just be on the left side. The end result is that motion will look jagged or stuttery. + +At the point that we're rendering, we actually know *exactly* how far between update frames we are: it's stored in `lag`. We bail out of the update loop when it's less than the update time step, but not when it's *zero*. That leftover amount? That's how far into the next frame we are. + +When we go to render, we'll tell it how far into the next frame we are: + +^code 10 + +The renderer knows each game object *and its current velocity*. Say that bullet is at (20, 300) and is moving right at 400 pixels per frame. If we are halfway between frames, then we'll end up passing 0.5 to `render()`. So it draws the bullet half a frame ahead, at (220, 300). Ta-da, smooth motion. + +Of course, it may turn out that that interpolation is wrong. When we actually calculate the next frame, we may discover the bullet hit an obstacle or slowed down or something. We rendered its position interpolated between where it was on the last frame and where we *think* it will be on the next frame. But we don't know that until we've actually done the full update with physics and AI. + +So the interpolation is a bit of a guess and sometimes ends up wrong. Fortunately, though, those kinds of corrections aren't usually noticeable by a player. At least, they're less noticeably than the stuttering you get if you don't interpolate at all. + +## Design Decisions + +### Do you own the game loop, or does the platform? + +The first decision you'll make is whether or not you explicitly have a game loop. Actually, it's probably clearer to say the first decision that will be made for you. If you're writing a game to run on a browser, you pretty much *can't* write your own classic game loop. The browser's event-based nature precludes it. Likewise, if you're using an existing game engine, you will probably rely on its game loop instead of rolling your own. + +* **Use the platform's event loop:** + + * *It's simple.* You don't have to worry about writing and optimizing the + core loop of the game. + + * *It plays nice with the platform.* Since the platform was designed with + this in mind, it usually interfaces nicely with it. You don't have to + worry about explicitly giving the host time to process its own events. + + * *You lose control over timing.* The platform will call your code as it + sees fit. If that's not as frequently, or as smoothly as you'd like, + too bad. Worse, most application event loops weren't designed with games + in mind and usually *are* slow and choppy. + +* **Use a game engine's event loop:** + + * *You don't have to write it.* Writing a game loop can get pretty tricky, + especially if things like threading come into play. Since that core code + gets executed every frame, minor bugs or performance problems can have a + large impact on your game. Having a solid event loop is one of the reasons + to consider using an existing engine. + + * *You don't get to write it.* Of course, the flip side to that coin is the + loss of control if you *do* have needs that aren't a perfect fit for the + engine. + +* **Write it yourself:** + + * *Total control.* You can do whatever you want with it. You can design it + specifically for the needs of your game. + + * *Have to interact with the platform yourself.* Application frameworks and + operating systems usually expect to have a slice of time to process events + and do other work periodically. If you own your app's core loop, it won't + get any. You'll have to explicitly hand off control periodically to make + sure the framework doesn't hang or get confused. + +### How does it adapt to different hardware? + + + +A game loop has two key pieces: non-blocking user input, and adapting to the +passage of time. The former is usually pretty straightforward. Most of the magic +is in how you deal with time. There are a near infinite number of platforms that +games can run on and any single game may run on quite a few. How it adapts to that is key. + +* **Fixed time step with no synchronization:** + + This was our first sample code. You just run the game loop as fast as you + can. + + * *It's simple*. This is its main (well, only) virtue. + + * *Game speed is directly affected by hardware and game complexity.* And + its main vice is that if there's any variation, it will directly affect + the game speed. + +* **Fixed time step with synchronization:** + + The next step up on the complexity ladder is running the game at a fixed + time step, but adding a delay or synchronization point at the end of the + loop to keep the game from running too fast. + + * *Still quite simple.* It's only one line of code more than the + probably-too-simple-too-actually-work example. In most game loops, you + will likely do synchronization *anyway*. You will probably [double + buffer](double-buffer.html) your graphics and synchronize the buffer + flip to the refresh rate of the display. + + * *It's power-friendly.* This is a surprisingly important consideration + for mobile games. You don't want to kill the user's battery + unnecessarily. By simply sleeping for a few milliseconds instead of + trying to cram ever more processing into each tick, you let the CPU idle + a bit which saves power. + + * *The game doesn't play too fast.* This fixes half of the speed concerns + of a fixed loop. + + * *The game can play too slow.* If it takes too long to update and render + a game frame, playback will slow down. Because this style doesn't + separate updating from rendering, it's likely to hit this sooner than + more advanced options. Instead of just dropping *rendering* frames to + catch up, this will just slow down. + +* **Variable time step:** + + I'll put this in here as an option in the solution space, with the caveat + that most game developers I see recommend against doing it. Still, it's good + to keep track of *why* it's a bad idea, lest we forget. + + * *Adapts to playing both too slowly and too fast.* If the game can't + keep up with real time, it will just take larger and larger time steps + until it does. + + * *Makes gameplay non-deterministic and unstable.* And this is the is the + real problem, of course. Physics and networking in particular become + much harder with a variable time step. + +* **Fixed update time step, variable rendering:** + + The last option we covered in the sample code is the most complex but also + the most adaptable. It updates with a fixed time step, but can drop + *rendering* frames if it needs to to catch up to the player's clock. + + * *Adapts to playing both too slowly and too fast.* As long as the game + can *update* in real time, the game won't fall behind. If the player's + machine is top-of-the-line, it will respond with a smoother gameplay + experience. + + * *It's more complex.* The main downside is there is a bit more going on + in the implementation. You have to tune the update time step to be both + as small as possible for the high-end, while not being too slow on the + low end. + + * *Updating can starve the renderer.* This style only renders once the + game state is caught up to the player's time. The update time step tends + to be smaller than your game's actual framerate on mid-level hardware. + The smaller the time step, though, the more time the game needs to spend + updating and the less time it has to render. The end result can be even + more dropped frames on low-end machines than you'd get with a simpler + loop and a longer update time step. + +## See Also + +* The classic article on game loops if Glenn Fiedler's [Fix Your Timestep](http://gafferongames.com/game-physics/fix-your-timestep/). This chapter wouldn't + be the same without it. + +* Witters' article on [game loops](http://www.koonsolo.com/news/dewitters-gameloop/) is a close runner-up. diff --git a/draft/first draft/introduction.markdown b/draft/first draft/introduction.markdown new file mode 100644 index 0000000..d0c18dc --- /dev/null +++ b/draft/first draft/introduction.markdown @@ -0,0 +1,310 @@ +^title Introduction + +When I was in fifth grade, my friends and I were given access to a +"resource center" center, a little unused classroom with a couple of +very beat up TRS-80s. A helpful teacher, hoping to inspire us, found a +printout of a bunch of simple BASIC programs and gave it to us. + +> I learned the value of short, simple code the hard way. + +Every morning, we'd carefully type in one that caught our eye. We had +to retype them every day since the audiocasette drives on the +computers were broken. Of course, this led us to prefer the programs +that were only a few lines long: + + 10 PRINT "BOB IS SUPER RAD" + 20 GOTO 10 + +Even then, the process was fraught with peril. We didn't know *how* +to program, so a tiny syntax error was impenetrable to us. We'd often +have to start over from scratch. + +But at the back of the stack of pages was a real monster: a program +that took up several dense pages of code. It took a while before we +worked up the courage to even try it, but it was irresistable: it was +called "Tunnels and Trolls". We had no idea what it did, but it sure +as hell sounded like a game, and I wanted to see it running so bad I +could taste it. + +We never did get it running, and after a year, we moved out of that +resource center. Much later when I actually knew a bit of BASIC, I +realized that it was just a character generator for the table-top +game, and not a game in itself. But the damage was already done. From +here on out, I wanted to be a game programmer. + +> A good chunk of my time was also spent catching snakes in the +> swamps of southern Louisiana. If it wasn't so blisteringly hot +> outside, there's a good chance this would be a herpetology book +> instead of a programming one. + +My family later got a Macintosh with QuickBASIC and later THINK C on +it. I spent almost all of my free time hacking together game programs. +Learning on my own was slow and painful, but rewarding too. I'd get +something up and running quickly: maybe a map screen, or a little +puzzle. But as I added features it got harder and harder. Once I +couldn't fit the entire program in my head anymore, it was doomed. + +This became a major motivation: how could I learn to write programs +bigger than would fit in my head? Instead of just reading about "How +to Program in C++", I started trying to find books about how to +*organize* programs. Sadly, the New Orleans B. Dalton bookstore wasn't +exactly a hotbed of software engineering resources. + +Fast-forward several years, and a friend hands me a new book: Design +Patterns. Finally! The book I'd been looking for since I was a +teen ager. I read it cover to cover in one sitting. I still struggled +with my own programs, but it was such a relief to see that other +people were running into the same problems, and coming up with +solutions. I felt like I finally had a couple of tools to use +instead of just my bare hands. + +In 2001, I finally got my dream job: software engineer at Electronic +Arts. I was going to be a real game programer! I couldn't wait to get +a look at some real game codebases and see the beautiful software +architecture they were using. Finally, I could see what a huge +shipping game codebase looked like. How could they make an enormous +game like Madden Football when it clearly was too big to fit in +anyone's head? What did they think of the Decorator pattern? Was +Observer as popular as I thought it would be? + +> Here's a simple set of rules to determine if Singleton is the best +> solution to your problem: +> 1. No, it isn't. + +What I saw surprised me. In many ways, the codebases weren't any +better at the architectural level than some of my own programs. Sure, +the *features* were incredible: great graphics, gameplay, audio. +Brilliant optimizations, clever use of the platform. But the +*architecture* that that brilliant code hung off of was often little +more than a pile of global variables. Coupling was so bad you often +couldn't even *define* modules, much less isolate them. As far as I +could tell, our engineers had read Design Patterns, but stopped after +the Singleton chapter. + +As time went on and codebases got bigger, I started seeing +improvement. I worked on a bunch of different games, some newer some +older. I started to see patches of architectural brilliance, and among +those isolated patches, I started seeing patterns. Many of them were +patterns I'd seen described in Design Patterns, but some I'd never +encountered outside of games. My coworkers were burning +overtime just to cram tiny features into their awful legacy codebase +and I wanted to shake them and say, "Hey! Look over here! Look how +they did this! See how much easier things could be?" + +This is what this book is about. It's my best attempt to harvest the +most useful patterns I've seen in game codebases and put them in an +easy to digest form so that hopefully my friends in the industry can +take the best of what other games are doing and apply it to their own +work. + +## What's In Store +There are already dozens of game programming books out there. What's +different about this one? I'll explain by analogy. + +Imagine a game codebase as a house. Graphics and sound are the +appliances and fixtures: a nice chandelier, kitchen sink, big picture +windows. This is the kind of stuff you can find in other books. It +matters, of course, but this book aims at something a bit more humble +and fundamental: the framing. Other books can teach you about windows +and fixtures, faucets and tubs. What I hope to cover in this book is +the joist and the arch, plumbing and wiring. Techniques that will let +you build an elegant and maintainable structure out of as few parts as +possible. Use them to frame the house, then consult those other books +to flesh it out. + +> This is the tragedy of software architecture: the better it is, the +> less it's noticed. All people will notice the features that were +> enabled by it. As the Tao To Ching says: "When the Sage's work is +> done, the people say, "We did it all by ourselves!" + +At the end of the day, people won't look at your home and say, +"Wow, nice wiring!" But without it, I can guarantee they'll notice +when your beautiful chandelier doesn't turn on. + +## How it Relates to Design Patterns +> In my opinion, a more important relationship is to Christopher +> Alexander's original A Pattern Language. It's about houses-and- +> buildings architecture, not software architecture, but there's +> plenty of insight in there on how to build things that humans love +> for any creative person to learn from. + +Any programming book with "Patterns" in its name clearly bears a +relationship to the classic Design Patterns: Elements of Reusable +Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson +and John Vlissides (usually referred to simply as the Gang of Four). + +By calling this book "Game Programming Patterns", I certainly don't +intend to imply that the Gang of Four's book doesn't apply. On the +contrary: I believe any programmer doing architecture-level work +should be familiar with Design Patterns, regardless of the domain they +code for. I look at this book as an extension of Design Patterns. + +You can think of Design Patterns as The Joy of Cooking. Regardless of +what you're preparing, there will be recipes in there for you. But if +you've decided to focus on baking, this book on desserts and pastries +is a helpful addition. If you can only buy one book, buy theirs. Not +only will it serve you in greater staid than this one, but this book +often presumes at least a passing familiarity with this. + +However, once you've got that on your bookshelf, I hope that I can +provide some more insight. Games are different from other software in +many ways. Time and sequencing are often a core part of a game's +architecture. Things must happen in the right order, and at the right +time. Most software's concept of time is only "the faster the better". + +Games often use random generation to provide a varied play experience, +where your average commercial program will never once call `rand()`. +There's some interesting implications of software that can create +novelty on its own, and other patterns won't help there. + +Once a game world is created or loaded, games must maintain and mutate +this complicated interacting world over time. In games, monsters may +run into each other, items may be picked, and bombs can blast enemies +and friends alike. Coordinating that kind of interaction is rare +outside of games. If you have two documents open in your word +processor, they don't talk to each other very much. + +This book will cover patterns that help untangle some of the issues +unique to games. We'll also cover some patterns that are in the Gang +of Four's book, but with an emphasis on how they're relevant to games. +If you've read their book but had trouble applying it to your work in +games this book may help. + +> *A Pattern Language* language covers human habitation from the +> largest to the smallest scale. An early pattern tells you where to +> place the cities in your country, useful advice for those of us +> terraforming new planets. A later pattern talks about how mismatched +> chairs are more appealing than a room full of identical furniture, +> explaining in real human terms why we love coffeeshops with funky +> thrift store sofas. + +Another difference is that this book is not just *design* patterns. +Some of the patterns will be smaller in scale than the architectural +patterns that the Gang of Four restricted themselves too. This, I +believe, follows more closely to Alexander's idea of a pattern +*language* that has patterns at all scales from the micro to the +macro. + +## How To Read This +This book is essentially structured like a recipe book, because in +every way that counts, it *is* one. + +This chapter introduces you to the material, and explains how the +rest of the book works. Its job is to provide context for the +patterns, how they relate to other books out there, and how it can be +applied to your coding. It tells you how to follow a recipe, prepare +the ingredients, and make sure you don't leave the oven on. + +Following that is the recipes of the cookbook: the patterns +themselves. The patterns are grouped into several themes (courses?). +Each theme describes two opposing forces encountered when building a +game. The patterns within that theme are tools you can use to find a +balance between those forces. + +For example, the "Communicating" theme is for patterns related +to how parts of a game communicate with each other. One force is that +game objects *must* interact: bullets must collide with enemies, the +hero must be able to play heroic sounds. At the same time, coupling is +generally bad and makes maintenance harder. The patterns in these +sections give you systems to allow modules to communicate with other +but in narrow ways that minimize the impact of the coupling. + +Within these themes are the patterns. Each pattern is described in a +fairly fixed structure. This is so you can treat this book as a +reference and quickly find what you need. + +Each pattern starts with an introduction. This describes the problem +that the pattern solves. This is first so that you can hunt through +the book quickly to find a pattern that will help you with your +current struggle. It's basically the sales pitch. + +If you've bought the pitch, the next section describes each step and +part of the pattern. Because patterns must always be *applied* to +some problem to be used, they are described here in terms of an +example usage. If you're comfortable with your programming language, +this section may be all you need. + +Following this is a longer implementation section. This takes the +example from the previous section and walks through an implementation +of the essence of the pattern. If this is the first time you've +encountered the pattern, this section will help you lock it into your +mind. + +At this point, you've got the pattern down. The remaining sections +expand on that knowledge and provide more context. Every design choice +is a trade-off, and the implications section will try to make sure you +understand both the benefits and costs of choosing this pattern. The +applications section shows common places in a codebase where the +pattern is used. Related patterns shows how this pattern ties in with +other patterns. Some patterns have a couple of common refinements you +see in practice that aren't part of the core pattern, but are useful +in their own right. For patterns that have these, the last section +will lay them out. + +## How the Book is Formatted +> Asides, extra information, and dumb jokes are placed in sidebars. +> Feel free to skip them if you want. + +Since this is a fairly high-level book, I kept the formatting as +simple as possible. Code samples are shown using a `monospace font`. + +Each pattern has a set of core components, the ingredients in its +recipe. Every implementation of the pattern will include these +components, so its important to note them all. The first time these +components are described they will be in **bold face**. + +

+need real page number here +

+ +When another pattern in this book is mentioned, it will be +capitalized. Following that will be the page number where the pattern +is defined, like Service (123). Patterns described by the Gang of Four +in *Design Patterns* will also be capitalized, but instead of a page +number, will be shown like Decorator (GoF). + +## About the Sample Code +Code samples in this book are in C++. This is not at all intended to +imply that these patterns are only useful in that language. Almost all +of them can be put to good use in any OOP language. + +I chose C++ for a couple of reasons. Most importantly, it's currently +the most popular language for programming commercially shipping games. +It is the linqua franca for this industry. Moreso, the core C syntax +C++ is based on is also the basis for Java, C#, and many other +languages. With a little bit of effort, a programmer in any of these +languages should be able to understand the code samples here. + +Keep in mind that the goal of this book is *not* to teach you C++. +The samples here are kept as simple as possible and don't represent +good C++ style or usage. Read the code samples for the idea being +expressed, not the code expressing it. + +To avoid wasting space on code you've already seen or that isn't +relevant to the pattern, code will sometimes be omitted in examples. +When this occurs, an ellipsis will be placed in the sample to show +where the missing code goes. + +Consider a function that will do some work and then return a value. +The pattern being explained is only concerned with the return value, +and not the work being done. In that case, the sample code will look +like: + + bool Update() + { + // do work... + + return IsDone(); + } + +## Where to Go From Here +This book is an example of the fact that patterns are a constantly +changing and expanding part of software development. The content in +this book comes from harvesting ideas from other codebases out there, +and that process will continue well beyond when this is printed. + +You are a core part of that process. As you develop your own patterns +and refine (or refute!) the patterns in this one, you contribute to +the software community. If you have suggestions, corrections, or other +feedback about what's in here, please get in touch. + diff --git a/draft/first draft/object-pool.markdown b/draft/first draft/object-pool.markdown new file mode 100644 index 0000000..fb891e0 --- /dev/null +++ b/draft/first draft/object-pool.markdown @@ -0,0 +1,478 @@ +^title Object Pool + +## Intent +*Improve performance and memory usage by creating a fixed pool of +objects up front and reusing them instead of allocating and freeing +individual objects.* + +## Motivation + +Let's say you're working on the visual effects for your game. When +the hero casts a spell, you want a burst of sparkles to shimmer +across the screen. This calls for a particle system, a little engine +that can spawn a bunch of individual sparkly particles and animate +them until they wink out of existence. + +Since a single wave of the wand could cause hundreds of particles to +be spawned, our particle system will need to be able to create +particles very quickly. Even more importantly, we need to ensure that +creating and destroying these particles doesn't cause memory +fragmentation. + +### The Curse of Fragmentation + +Programming a game for a dedicated game console like the XBox 360 in +many ways has more in common with embedded programming than it does +with convention PC programming. Like embedded programming, console +games must run continuously for a very long time without crashing or +leaking memory and efficient compacting memory managers are +rarely available. This means memory fragmentation is dreaded. + +Fragmentation means the free space in your heap is broken into smaller +separate regions of memory instead of one large open space. The total +amount of memory available may be large, but the largest region of +*contiguous* memory can be painfully small. When you try to allocate +a chunk of memory for your object, even though there is that much +memory scattered across the heap, it can't find a single chunk big +enough and the allocation fails. Sad faces all around. + +
+initial heap is empty
+                            free
+                            ...................................
+
+allocate an object 'foo'
+                            foo      free
+                            [|||||||]..........................
+                            
+allocate an object 'bar'    
+                            foo      bar            free
+                            [|||||||][|||||||||||||]...........
+
+delete the first object     
+                            free     bar            free
+                            .........[|||||||||||||]...........
+ 
+the free space is now fragmented into two regions
+try to allocate another bar
+                            free     bar            free
+                            .........[|||||||||||||]...........
+                            bar      XXXXXX     XXXX        bar
+won't fit in either space   [|||||||||||||]     [|||||||||||||]
+
+ +> Most console makers require games to pass "soak tests" where they +> leave the game running in demo mode for several days. If the game +> crashes, they don't allow it to ship. While soak tests can sometimes +> crash because of a rare bug, it's usually creeping fragmentation +> or memory leakage that brings the game down. + +Fragmentation can occur slowly but may still gradually reduce the +heap to an unusable foam of open holes and filled bits, ultimately +hosing the game completely. + +Because of this, and because allocation can be slow, most games are +very careful about when and how they manage memory. For many cases, +the simplest solution is best: grab a big chunk of memory when the +game starts and don't mess with it too much while its running. + +An object pool gives us the best of both worlds: to the memory +manager, we're just allocating one big hunk of memory up front and +not freeing it while the game is playing. To the users of the particle +system, we can freely allocate and deallocate particles to our heart's +content. + +## The Pattern + +Define a **pool** class that maintains a collection of **reusable +objects**. Each object, in addition to whatever state it needs for its +own use, also has an **"in use" flag** to mark whether or not it is +currently "live". When the pool is initialized, it creates the entire +collection of objects up front (usually in a single contiguous +allocation) and initializes them all to not in use. + +When you want a new object, you ask the pool for one. It finds the +first available object, initializes it to "in use" and gives it back +to you. When the object is no longer needed, it is set back to the +"not in use" state. This way, objects can be freely created and +destroyed without needing to allocate memory or other resources. + +## When to Use It + +This pattern is used widely in games for obvious things like game +entities and visual effects, but also for less visible data structures +such as currently playing sounds. Use an object pool when you need to +frequently create and destroy objects of a certain type and: + +* allocating them on the heap is slow or could lead to memory + fragmentation. + +* the object encapsulates a resource such as a database or network + connection that is slow to acquire and could be reused. + +## Parts + +### ReusableObject + +* Tracks whether or not it is in use. + +* Can be reinitialized to become a "new" object. + +### ObjectPool + +* Owns a collection of ReusableObjects + +* Creates new ReusableObjects by reinitializing ones from its + collection. + +## Keep in Mind + +### Memory for the pool will be taken regardless of use. + +The pool will be babysitting its pile of objects whether or not they +are actually used. When tuning pool sizes, we're usually carefuly to +make sure they're big enough, since making them too small will often +lead to a crash. Also important is making they aren't *too* big. A +smaller pool frees up memory that could be used in other ways to make +the game better. + +### Only a fixed number of objects can be active at any one time. + +In some ways this is a good thing. By partitioning memory into +separate pools for different types of objects, you ensure that, for +example, a huge sequence of explosions won't cause your particle +system to eat all of the available memory, preventing something more +critical from being allocated. + +Nonetheless, this also means you will need to handle the case where +you try to create a new object and it fails because the pool is full. +There are a couple of common strategies for this. + +1. Just don't create the object. Sounds harsh, but this makes sense + for some things like our particle system example. If all particles + are in use, the screen is probably full of flashing graphics. The + user won't notice if the next explosion isn't quite as impressive + as the ones currently going off. + +2. Forcibly kill an existing object. Consider a pool for managing + currently playing sounds. Let's say we want to start a new sound + but the pool is full. We definitely *don't* want to simply ignore + the new sound: the user will notice if their magical wand only + swishes dramatically half the time. A better solution is to find + the quietest sound already playing and replace that with our new + sound. The new sound will mask the audible cutoff of the previous + sound. In general, if the *absence* of a new object would be more + noticeable than the *disappearance* of an existing one, this can + be the right choice. + +3. Increase the size of the pool. If your game does let you be a bit + more flexible with memory, you may be able to increase the size of + the pool. If you choose this, make sure to consider whether or not + the pool should contract to its previous size if the additional + capacity is no longer needed. + + A single fixed pool size may not be the best fit for all game + states. Some levels may be effect heavy while others are sound + heavy. In that case, it may be useful to support different pool + sizes for different scenarios. + +### Unused objects will remain in memory. + +This doesn't generally matter, but if you are also using a garbage +collector, this can prevent it from freeing some memory. If your +pooled object has a reference to some other object, the garbage +collector has no way of knowing that that reference is "dead" when the +pooled object is not in use. + +Many garbage collectors perform very well and can even compact memory +and reduce fragmentation. If you are using one, consider carefully +whether or not you even need to use an object pool for memory or +performance reasons. + +### Memory size for each object is fixed. + +Most object pool implementations store the objects in an array of +in-place objects. If all of your objects are of the same type, this is +fine. However, if you want to store derived objects in the pool, or +objects of different types, you need to ensure that each slot in the +pool has enough memory for the largest possible object. + +> This is a common pattern for implementing speed-efficient memory +> managers. The manager has a number of pools of different block +> sizes. When you ask it to allocate a block, it finds in an open slot +> in the pool of the appropriate size and allocates from that pool. + +This leads to another consideration. If your objects do vary in size, +you could end up wasting memory. Each slot will need to be big enough +to accomodate the largest type. If that type is rarely used and is +much bigger than the more common types, you're throwing away memory. +When this happens, it may be useful to split the pool into separate +pools for different sizes of object. + +### Reused objects aren't automatically cleared. + +> It might be worth adding a debug feature to your object pool class +> that will clear the memory for an object to some known value when +> the object is no longer in use. That way, when that slot is used +> again later, it's easy to see which variables in the object haven't +> been initialized. + +Most memory managers will zero out memory before use, or +initialize to some obviously wrong value, at least in debug builds. +This can help you find painful bugs caused by uninitialized variables. +Since the object pool isn't going through the memory manager every +time it reuses an object, we won't get that safety net anymore. Worse, +the memory it's using for the "new" object previously held an object +of the exact same time, so it can be nigh impossible to tell from +looking at it if you forgot to initialize something when you created +the new object. + +Because of this, pay special care that the code that initializes new +objects in the pool fully initializes the object. + +## Design Decisions + +At its simplest, an object pool is an almost trivial pattern: you +have an array of objects and you reinitialize them periodically. But +there are a bunch of ways you can build on top of that to make the +pool more generic, safer to use, or easier to maintain. As you +implement them in your games, you'll need to answer these questions: + +### Are objects coupled to the pool? + +The first question you'll run into when writing an object pool is +deciding if the objects directly defined to be used in a pool, or +whether pooling is a separate system that happens to pool those +objects. Most of the time, the answer will be that the objects are +defined to be pooled, but if you are trying to write a generic pool +class that can pool arbitrary objects, that can be an undesireable +limitation. + +* **If objects are explicitly bound to the pool:** + + * *The implementation is simpler.* You can simply put the "in + use" flag or function in your pooled object and be done with + it. + + * *You can programmatically ensure that the objects can *only* + by created by the pool.* A simple way to do this is to make + the pool class a friend of the object class, and then make the + object's constructor private. + + ^code 10 + + This helps make sure your users don't create objects that + aren't correctly tracked by the pool and points them towards + the right way to use the class. + + * *You may be able to avoid storing an explicit "in use" flag.* + Many objects already retain some state that could be used to + tell if it's live or not. For example, a particle may be + available for reuse if it's current position is offscreen. If + the object class knows it may be used in a pool, it can + provide an `InUse()` method to query that state. This saves + the pool from having to burn some extra memory storing a bunch + of "in use" flags that aren't needed. + +* **If objects are not coupled to the pool:** + + * *Objects of any type can be pooled.* This is the big + advantage. By decoupling objects from the pool, you may be + able to implement a generic reusable pool class. + + * *The "in use" state must be tracked elsewhere.* The simplest + way to do this is by having a separate bit field. + + ^code 11 + +### What is responsible for initializing the reused objects? + +In order to reuse an existing object, it must be reinitialized with +new state. A key question here is does this happen inside the pool +class, or outside? + +* **If the pool handles it internally:** + + * *The pool can completely encapsulate its objects*. Depending + on the other capabilities your objects need, you may be able + to keep them completely internal to the pool. This can make + sure that other code doesn't maintain references to objects + that could be unexpectedly reused. + + * *The pool is tied to how objects are initialized*. A pooled + object may have multiple different ways it can be initialized. + If the pool manages this, its interface needs to support all + of those different mechanisms and forward them to the object. + + ^code 12 + +* **If outside code initializes it:** + + * *The pool relies on outside code to correctly initialize + objects.* This is the most obvious implication, but an + important one to consider. The pool won't be able to ensure + that objects are placed in a correct state when they are + reused. + + * *The pool's interface can be simpler.* Instead of multiple + functions for each way an object can be initialized, the + pool can simply return a reference to the new object. + + ^code 13 + + The caller can then initialize it directly using whathever is + appropriate. + + ^code 14 + + * *Outside code may need to handle failure to create a new + object.* The previous example code assumes that `Create()` + will always successfully return a new object. If the pool is + full, though, that may fail to happen. You'll need to make + sure your calling code handles that case too, if it can occur. + +## Sample Code + +### First Pass + +Real-world particle systems will often apply gravity, wind, +friction, and other physical effects. Our much simpler sample will +just move particles in a straight line for a certain number of frames +and then kill the particle. Not exactly film calibur, but it should +get the job done. First up is the little particle class. + +^code 1 + +It provides a single default constructor that initializes it to not +in use. It relies on a later call to its `Init()` function to +initialize it to a live state. + +To animate the particles over time, an `Animate()` function is +provided. The game is expected to call this once per frame for each +live particle. + +The pool will need to know which particles are available for reuse, +and this class provides that with the `InUse()` function. That +function takes advantage of the fact that particles have a limited +lifetime, and uses that to know which particles are in use, without +having to store a separate flag. + +The pool class is also simple. + +^code 2 + +It provides a `Create()` function for external code to create new +particles. `Animate()` should be called once per frame by the game, +and it simply calls the same method on each particle in the pool. + +The particles themselves are simply stored in a fixed-size array in the +class. In this sample implementation, the pool size is hardcoded in +the class, but this could be tunable externally by using a dynamic +array of a given size, or using a value template parameter. + +Creating a new particle is pretty straightforward. + +^code 3 + +We simply iterate through the pool looking for the first available +particle. When we find it, we initialize it and we're done. Note that +in this implementation, if there aren't any available particles, we +simply don't create a new one. + +That's pretty much all there is to a simple implementation. We can now +create a pool, and create some particles using it. The particles will +automatically free themselves up when their lifetime has expired. + +> O(n) complexity, for those of us who remember our algorithms class. + +For many use cases, this is good enough, but keen eyes may have +noticed that creating a new particle requires iterating through +(potentially) the entire collection until we find an open slot. If +the pool is very large and mostly full, that can get slow. Let's see +if we can improve that without sacrificing any memory. + +### Speeding Up Creation + +Ideally, we'd store a separate collection of pointers to all of the +free particles in the pool. Then when we need to create a new one, we +can just pull the first pointer off of our free list and insert the +new particle there. + +The problem is that that would require us to +maintain an entire other array with as many pointers as there are +objects in the pool. It would be nice to not have to give up any +memory to get this speed improvement. Fortunately, there *is* some +memory laying around we can use: *the slots for the unused objects +themselves.* + +When a particle isn't in use, most of it's state is +irrelevant. It's position and velocity aren't being used anyway. For +a dead particle, the only state it needs is the minimim required to +tell if it's dead. For our example, that's the `_framesLeft` member. +Everything else we can reuse. Here's a revised particle: + +^code 4 + +We've gotten all of the member variables except for `_framesLeft` and +moved them into a struct inside union. This `live` struct will hold +the particle's state when it's in use. When it's available, the other +case of the union, the `next` member will be used. It holds a +pointer to the *next* available particle. + +The idea is that we'll use the memory from all of the *dead* particles +to create a linked list that threads its way through them. Now we have +our collection of pointers to available particles, we're just +interleaving it into the particles themselves. + +For this to work, we need to make sure this list is set up correctly, +and is is correctly maintained when particles are created and +destroyed. And, of course, we'll need to keep track of the list's +head: + +^code 5 + +When a pool is first created, all of the particles are available, so +our list should thread through the entire pool. We'll make the pool +constructor set that up: + +^code 6 + +> O(1), baby! Now we're cooking with fire! + +Now, to create a new particle, we can jump directly to the first +available one: + +^code 7 + +And finally, when a particle gives up the ghost, we'll thread it back +onto the list: + +^code 8 + +## See Also + +### Flyweight (GoF) + +On the surface, this looks a lot like the Flyweight (GoF) pattern. +Both maintain a collection of small objects so that they can be +reused. The difference here is what is meant by "reuse". Flyweight +objects are intended to be reused by sharing the same instance between +multiple owners simultaneously. Flyweight is about avoiding duplicate +memory usage. + +Pooled objects are not intended to be used that way. The objects in a +pool get reused, but only over time. "Reuse" in the context of an +object pool means reclaiming the *memory* for an object *after the +original owner is done with it*. With an object pool, there isn't +any expectation that an object will be shared within its lifetime. + +

+circular buffer implementation? + +* all objects should have same lifetime +* handles the pool overflowing by killing and replacing the oldest + object with the new one +* useful for things like particle systems where the total lifetime + isn't too critical, but staying within an easily adjustable memory + footprint is +

diff --git a/draft/first draft/service-locator.markdown b/draft/first draft/service-locator.markdown new file mode 100644 index 0000000..eac3c1b --- /dev/null +++ b/draft/first draft/service-locator.markdown @@ -0,0 +1,602 @@ +^title Service Locator +^theme Communicating + +## Intent + +*Provide a global point of access to a service without coupling users +to the concrete class that implements it.* + +## Motivation + +Some objects or systems in a game tend to get around, visiting almost +every corner of the codebase. It's hard to find a part of the game +that *won't* need a memory allocator, logging, the file system, or +random numbers at some point. Systems like those can be thought of as +*services* that need to be available to the entire game. + +For our example, we'll consider audio. It doesn't have quite the reach +of something lower-level like a memory allocator, but it still touches +a bunch of game systems: A falling rock hits the ground with a crash +(physics). A sniper NPC fires his rifle and a shot rings out (AI). The +user selects a menu item with a beep of confirmation (user interface). + +Each of these places will need to be able to call into the audio +system, with something like one of these: + +^code 15 + +Either gets us where we're trying to go, but we stumbled into some +sticky coupling along the way. Every place in the game calling into +our audio system directly references the concrete AudioSystem class +and the mechanism for accessing it -- either as a static class or +a Singleton. + +These callsites, of course, have to be coupled to *something* in order +to make a sound play, but letting them poke directly at the concrete +audio implementation is like giving a hundred strangers directions to +your house just so they can drop a letter on your doorstep. Not only +is it a little bit *too* personal, it's a real pain when you move and +you have to tell each person the new directions. + +There's a better solution: a phone book. People that need to get in +touch with us can look us up by name and get our current address. When +we move, we tell the phone company. They update the book, and everyone +gets the new address. In fact, we don't even need to give out our real +address at all. We can list a P.O. box or some other "representation" +of ourselves instead. By having callers go through the book to find +us, we have *a convenient single place where we control how we're +found.* + +This is the Service Locator pattern in a nutshell: it decouples code +that needs a service from both *who* it is (the concrete +implementation type) and *where* it is (how we get to the instance of +it). + +## The Pattern + +A **service** class defines an abstract interface to a set of +operations. A concrete **service provider** implements this interface. +A separate **service locator** provides access to the service by +finding an appropriate provider while hiding from both the provider's +concrete type and the process used to locate it. + +## Parts + +### Service + +*

Defines the set of operations the service exposes.

+ +### Service Provider + +* Concrete implementation of Service interface. + +* May be more than one of these. + +### Service Locator + +* Locates or creates an appropriate Service Provider. + +* Provides global access to the Service. + +## When to Use It + +If a large number of places in the codebase need access to the same +object and there isn't an obvious way for them to get it, this pattern +is often a good solution. It's usually more flexible than a static +class, and more maintainable than a Singleton. + +One limitation is that the implementation of the service doesn't know +*who* is using it or what for. This means it must be able to work +correctly in any circumstance. For example, a class that expects to +only be used during the simulation portion of the game loop and not +during rendering may not work as a service -- it wouldn't be able +to ensure that it's being used at the right time. So, if our class +only expects to be used within a certain context, it may be safest to +avoid exposing it to the world with this pattern. + +## Keep in Mind + +### The Service is Globally Accessible + +This pattern shares a problem with the classic Singleton pattern: it's *global.* This is +convenient for code that needs the service, but opens the door to +coupling and maintenance headaches if the wrong code starts using the +service. + +### The Service Actually Has to Be Located +With a Singleton or a static class, there's no chance for the instance +we need to *not* be available. Calling code can take for granted that +it's there. But since this pattern has to *locate* the service, we may +need to handle cases where that fails. Fortunately, we'll cover a +strategy later to address this and guarantee that we'll always get +*some* service when you need it. + +## Sample Code + +Getting back to our audio system problem, let's address it by exposing +the system to the rest of the codebase through a service locator. + +### The Service + +We'll start off with the audio API. This is the interface that our +service will be exposing: + +^code 9 + +A real audio engine would be much more complex than this, of course, +but this shows the basic idea. What's important is that it's an +abstract interface class with no implementation bound to it. + +### The Service Provider + +By itself, our audio interface isn't very useful. We need a concrete +implementation. This book isn't about how to write audio code for a +game console, so you'll have to imagine there's some actual code in +the bodies of these functions, but you get the idea: + +^code 10 + +Now we have an interface and an implementation. The remaining piece is +the service locator -- the class that ties the two together. + +### A Simple Locator + +The implementation we'll see here is about the simplest kind of +service locator you'll see: + + + +^code 1 + +The static `GetAudio()` function does the locating -- we can call +it from anywhere in the codebase and it will give us back an instance +of our `IAudio` service to use: + +^code 5 + +The way it "locates" is very simple: it relies on some outside code +somewhere to register a service provider with it before any other code +tries to use the service. When the game is starting up, it will call +some code like this: + +^code 11 + +The key part to notice here is that our `SomeGameCode()` function +isn't aware of the concrete `ConsoleAudio` class, just the abstract +`IAudio` interface. Equally important, not even the *locator* class is +coupled to the concrete service provider. The *only* place in code +that knows about the actual concrete class is the initialization +function that registers the service. + +There's one more level of decoupling here: the `IAudio` interface +isn't aware of the fact that it's being accessed in most places +through a service locator. As far as it knows, it's just a regular +abstract base class. This is useful because it means we can apply this +pattern to *existing* classes that weren't necessarily designed around +it. This is in contrast with Singleton, which affects the design of the +"service" class itself. + +### A Null Service + + + +Our implementation so far is certainly simple, and it's pretty flexible too. But it has one big shortcoming: if we try to use the +service before a provider has been registered, it returns `NULL`. If +the calling code doesn't check that, we're going to crash the game. + +Fortunately, there's another design pattern called "Null Object" we +can use to address this. The basic idea is that in places where we +would return `NULL` when we fail to find or create an object, we +instead return a special object that implements the same interface as +the desired object. Its implementation basically does nothing, but +allows code that receives the object to safely continue on as if it +had received a "real" one. + +To use this, we'll define another "null" service provider: + +^code 7 + +As you can see, it implements the service interface, but doesn't +actually do anything. Now, we change our locator to this: + + + +^code 8 + +Calling code will never know that a "real" service wasn't found, nor +does it have to worry about handling `NULL`. It's guaranteed to +always get back a valid object. + + + +This is also useful for *intentionally* failing to find services. If +we want to disable a system temporarily, we now have an easy way to do +so: simply don't register a provider for the service and the locator +will default to a null provider. + +### Logging Decorator + +Now that our system is pretty robust, let's discuss another refinement +this pattern lets us do: decorated services. I'll explain with an +example: + +It's helpful during development to have the game log when certain +events occur. If you're working on AI, it's very useful to know when +an entity is changing AI states. If you're the sound programmer, you +may want a record of every sound as it plays so you can check that +they trigger in the right order. + +The typical solution is to just litter the code with calls to some +`Log()` function. Unfortunately, that replaces one problem with +another: now we have *too much* logging. The AI coder really doesn't +care when sounds are playing, and the sound person doesn't care about +AI state transitions, but now they both have to wade through each +other's messages. + +Ideally, we would be able to selectively enable logging for just the +stuff we care about, and in the final game build, there'd be no +logging at all. If the different systems we want to conditionally log +are exposed as services, then we can solve this using the Decorator pattern. Let's +define another audio service provider implementation like this: + +^code 12 + +As you can see, it wraps another audio provider and exposes the same +interface. It forwards the actual audio behavior to the inner +provider, but also logs each sound call. If a programmer wants to +enable audio logging, they call this: + +^code 13 + +Now any calls to the audio service will be logged before continuing as +before. And, of course, this plays nicely with our null service, so +you can both *disable* audio and yet still log the sounds that it +*would* play if sound were enabled. + +## Design Alternatives + +We've covered a typical implementation, but there's a couple of ways +that it can vary, based on differing answers to a few core questions: + +### How Is the Service Located? + +* **Outside code registers it:** + + This is the mechanism our sample code uses to locate the service, + and is the most common design I see in games: + + * *It's fast and simple.* The `Locate()` function simply returns + a pointer. It will often get inlined by the compiler, so we + get a nice abstraction layer at almost no performance cost. + + * *We control how the provider is constructed.* Consider a + service for accessing the game's controllers. We have two + concrete providers: one for regular games and one for playing + online. The online provider passes controller input over + the network so that, to the rest of the game, remote players + appear to just be using local controllers. + + To make this work, the online concrete provider needs to know + the IP address of the other remote player. If the locator + itself was constructing the object, how would it know what to + pass in? The locator class doesn't know anything about online + at all, much less some other user's IP address. + + Externally registered providers dodge the problem. Instead of + the locator constructing the class, the game's networking code + instantiates the online-specific service provider, passing in + the IP address it needs. Then it gives that to the locator, + who knows only about the service's abstract interface. + + * *We can change the service while the game is running.* We + may not use this in the final game, but it's a neat trick + during development. While testing, we can swap out, for + example, the audio service with the null service we talked + about earlier to temporarily disable sound while the game is + still running. + + * *The locator depends on outside code.* This is the downside. + Any code accessing the service presumes that some code + somewhere has already registered it. If that initialization + doesn't happen, we'll either crash or have a service + mysteriously not working. + +* **Bind to it at compile time:** + + The idea here is that the "location" process actually occurs at + compile time using preprocessor macros. Like so: + + ^code 2 + + Locating the service like this implies a few things: + + * *It's fast.* Since all of the real work is done at compile + time, there's nothing left to do at runtime. The compiler + will likely inline the `Locate()` call, giving us a solution + that's as fast as we could hope for. + + * *You can guarantee the service is available.* Since the + Locator owns the service now and selects it at compile time, + we can be assured that if the game compiles, we won't have to + worry about the service being unavailable. + + * *You can't change the service easily.* This is the major + downside. Since the binding happens at build time, any time + you want to change the service, you've got to recompile and + restart the game. + + + +* **Configure it at runtime:** + + Over in the khaki-clad land of enterprise business software, if + you say "service locator", this is what they'll have in mind. When + the service is requested, the locator does some magic at runtime + to hunt down the actual implementation requested. + + Typically, this means loading a configuration file that identifies + the provider, then using reflection to instantiate that class at + runtime. This does a few things for us: + + * *We can swap out the service without recompiling.* This is a + little more flexible than a compile-time bound service, but + not quite as flexible as a registered one where you can + actually change the service while the game is running. + + * *Non-programmers can change the service.* This is nice for + when the designers want to be able to turn certain game + features on and off, but aren't comfortable mucking through + source code. (Or, more likely, the *coders* aren't comfortable + with them mucking through it.) + + * *The same codebase can support multiple configurations + simultaneously.* Since the location process has been moved out + of the codebase entirely, we can use the same code to support + multiple service configurations simultaneously. + + This is one of the reasons this model is appealing over in + enterprise web-land: you can deploy a single app that works on + different server set-ups just by changing some configs. It's + less useful in games. Console hardware is pretty well + standardized, and even PC games target a certain baseline + specification. + + * *It's complex.* Unlike the previous solutions, this one is + pretty heavyweight. You have to create some configuration + system, possibly write code to load and parse a file, and + generally *do some stuff* to locate the service. Time spent + writing this code is time not spent on other game features. + + * *Locating the service takes time.* And now the smiles really + turn to frowns. Going with runtime configuration means you're + burning some CPU cycles locating the service. Caching can + minimize this, but that still implies that the first time you + use the service, the game's got to go off and spend some time + hunting it down. Game developers *hate* burning CPU cycles on + something that doesn't improve the player's game experience. + +### What happens if service could not be located? + +* **Let the user handle it:** + + The simplest solution is to pass the buck. If the locator can't + find the service, it just returns `NULL`. This implies: + + * *It lets users determine how to handle failure.* Some users + may consider failing to find a service a critical error that + should halt the game. Others may be able to safely ignore it + and continue. If the locator can't define a blanket policy + that's correct for all cases, then passing the failure down + the line lets each callsite decide for itself what the right + response is. + + * *Users of the service must handle the failure.* Of course, + the corollary to this is that each callsite *must* check for + failure to find the service. If almost all of them handle + failure the same way, that's a lot duplicate code spread + throughout the codebase. If just one of the potentially + hundreds of places that use the service fails to make that + check, our game is going to crash. + + + +* **Halt the game:** + + I said that we can't *prove* that the service will always be + available at compile-time, but that doesn't mean we can't + *declare* that availability is part of the runtime contract of the + locator. The simplest way to do this is with an assertion: + + ^code 4 + + If the service isn't located, the game stops before any subsequent + code tries to use it. The `assert()` call there doesn't solve the + problem of failing to locate the service, but it does make it + clear whose problem it is. By asserting here, we say, "failing to + locate a service is a bug in the locator." + + So what does this do for us? + + * *Users don't need to handle a missing service.* Since a single + service may be used in hundreds of places, this can be a + significant code saving. By declaring it the locator's job to + always provide a service, we spare the users of it from having + to pick up that slack. + + * *The game is going to halt if the service can't be found.* + On the off chance that a service really can't be found, the + game is going to halt. This is good in that it forces us to + address the bug that's preventing the service from being + located (likely some initialization code isn't being called + when it should), but it's a real drag for everyone else who's + blocked until it's fixed. With a large dev team, you can incur + some painful programmer downtime when something like this + breaks. + +* **Return a null service:** + + We showed this refinement in our sample implementation. Using this + means: + + * *Users don't need to handle a missing service.* Just like + the previous option, we ensure that a valid service object + will always be returned, simplifying code that uses the + service. + + * *The game will continue if the service isn't available.* This + is both a boon and a curse. It's helpful in that it lets us + keep running the game even when a service isn't there. This + can be really helpful on a large team when a feature we're + working on may be dependent on some other system that isn't + in place yet. + + The downside is that it may make it harder to debug a missing + service. Imagine that some code in the game uses a service to + access some data and then makes a decision based on it. If it + gets a null service instead of a real one and gets dummy data + from it, the game may not behave as expected. It could take + effort to trace that issue back to the fact that a service + wasn't there when we thought it would be. + +Among these options, the one I see used most frequently is simply +asserting that the service will be found. By the time a game gets out +the door, it's been very heavily tested, and it will likely be run on +a reliable piece of hardware. The chances of a service failing to be +found by then are pretty slim. + +On a larger team, I encourage you to throw a null service in. It +doesn't take much effort to implement, and can spare you from some +downtime during development when a service isn't available. It also +gives you an easy way to turn a service off if it's buggy or is just +distracting you from what you're working on. + +### What Is the Scope of the Service? + +Up to this point, we've assumed that the locator will provide access +to the service to *anyone* who wants it. While this is the typical way +the pattern is used, another option is to limit access to a single +class and its descendants, like so: + +^code 3 + +With this, access to the service is restricted to classes that inherit +`Base`. There are advantages either way: + +* **If access is global:** + + * *It encourages the entire codebase to all use the same + service.* Most services are intended to be singular. By + allowing the entire codebase to have access to the same + service, we can avoid random places in code instantiating + their own providers because they can't get to the + “real” one. + + * *We lose control over where and when the service is used.* + This is the obvious cost of making something global: anything + can get to it. The Singleton chapter has a full cast of + characters for the horrorshow that global scope can spawn. + +* **If access is restricted to a class:** + + * *We control coupling.* This is the main advantage. By limiting + a service to a branch of the inheritance tree, we can make + sure systems that should be decoupled stay decoupled. + + * *It can lead to duplicate effort.* The potential downside is + that if a couple of unrelated classes *do* need access to the + service, they'll each need to have their own reference to it. + Whatever process is used to locate or register the service + will have to be duplicated between those classes. + + (The other option is to change the class hierarchy around to + give those classes a common base class, but that's probably + more trouble than it's worth.) + +My general guideline is that if the service is restricted to a single +domain in the game then limit its scope to a class. For example, a +service for getting access to the network can probably be limited to +online classes. Services that get used more widely like logging should +be global. + +## See Also + +* The Service Locator pattern is a sibling to Singleton in many ways, so it's worth + looking and both to see which is most appropriate for your needs. + +* Microsoft's XNA Framework for game development has this pattern + built into its core `Game` class. Each instance has a + `GameServices` object that can be used to register and locate + services of any type. \ No newline at end of file diff --git a/draft/first draft/singleton.markdown b/draft/first draft/singleton.markdown new file mode 100644 index 0000000..093ed6d --- /dev/null +++ b/draft/first draft/singleton.markdown @@ -0,0 +1,438 @@ +^title Singleton + +If I had to reduce this chapter to one word, it would be: *don't*. +The Singleton pattern is the most-used design pattern in games, and +the least needed. In most cases, using it actually make things worse. +Before I try to convince you why it's a bad idea and show you some +alternatives, I suppose I'm obligated to at least explain what the +pattern is. + +## The Singleton Pattern + +*Design Patterns* summarizes Singleton like this: + +> Ensure a class has one instance, and provide a global point of +> access to it. + +Let's split at the "and" and consider each part separately. + +### Ensuring a Class Has One Instance + +There are times when a class can only perform correctly only if +there is a single instance of it. This usually happens when your class +wraps or communicates with an external system that maintains its own +global state. + +For example, let's say you're writing a class that wraps the +platform's underlying file system. Because file system operations can +be slow, your class handles most operations asynchronously. These +operations need to be coordinated with each other. If you start one +asynchronous operation to create a file, and another one to delete +that same file, you want to make certain that those two operations are +coordinated. + +If users can freely create instances of your file system wrapper, +those instances have no way of coordinating with each other. Enter the +singleton: a pattern for letting a class itself ensure that it will +have only a single instance. + +### Providing a Global Points of Access + +Lots of places in our codebase may need to read and +write files. If they can't create their own instances of our file +system wrapper, how do they do that? This is the other half of the pattern. In addition to +creating the single instance, we also provide a globally-available +method to get it. This way, anyone anywhere can get their paws on our blessed instance. + +Solving both of those, the canonical singleton implementation looks like this: + +^code 1 + +The static `sInstance` member will hold an instance of +the class. The private constructor ensures that no other code can +create their own FileSystems, so there can be no more than one. The +public static `Instance()` method lets any place in the codebase +access the instance. It is also responsible for creating the instance +using lazy initialization. It will instantiate it the first time +someone asks for it. + +## The Good Points + +Right off, our implementation has a few good things going for it. + +* **It's convenient.** This the big win, and the reason it's used + so much. With this simple implementation, it's easy as pie for + anywhere in our codebase to get at the file system and do what it + needs. It's the drive-thru fast food joint of design patterns. + +* **It doesn't allocate the Singleton if no one uses it.** Saving + memory and cycles is always good. Since the instance is lazy + initialized, if it's *never* accessed, it will never get created + at all. + +* **You can subclass the Singleton.** This is a great but often + overlooked feature. It isn't necessary for the `Instance()` + function to actually allocate an instance of `FileSystem`. + Instead, we can create FileSystem as an abstract file system + interface, and defer the implementation to a concrete subclass. + First, let's define a file system interface, and two + platform-specific implementations: + + ^code 2 + + Now we turn `IFileSystem` into a Singleton: + + ^code 3 + + The fun part is how the instance is initialized. Instead of just + creating an instance of its own class, it now chooses an + appropriate platform-specific derived class: + + ^code 4 + + The end result is that our entire codebase can access the file + system using `IFileSystem::Instance()`, but only the + implementation file for the `IFileSystem` class itself is coupled + to any of the platform-specific code. + +## Then the Trouble Starts + +I said Singleton is the fast food of the design pattern world, and like a greasy burger, it goes down easy but after a while you start +to regret eating them for every meal. Unfortunately, while the benefits of it are immediately apparent, the problems it causes may +not be obvious for a while: + +### It's a glorified global variable. + +Smart programmers who came before us learned the hard way how much +pain global state can cause. They've passed on that legacy by beating +into our heads "globals = bad". However, doing clean object-oriented +design *without* any global state is surprisingly hard. + +Many game programmers come from a low-level procedural background. +When you're coding in assembly or C, globals and statics are a common +way to solve problems. Moving to C++ and being told not to do that +anymore feels like a major tool in the toolbox has been taken away. + +The Singleton pattern feels like a guilt-free way to get that tool +back. Sadly, it's a sham. While the pattern wraps the global state up +into something more OOP-friendly, it doesn't really solve the problems +global state causes any more than pouring low-fat dressing on a +quarter-pounder with cheese makes it healthier. + +If you only learned to avoid globals because it's the Right Thing To +Do, here's a quick review on the trouble they cause: + + + +* **It makes it harder to reason about code.** Let's say you're + reading a function someone else wrote, trying to figure out what's + causing a bug. If that function doesn't touch any global state, + then all you need to hold in your head is the arguments being + passed in, and the code for the function itself. + + Now imagine right in the middle of that function is a call to + `SomeClass::GetSomeGlobalData()`. Now, to understand that piece of + code, you have to hunt through the entire codebase and see what + could be changing that global data. You don't really hate globals + until you've had to grep an entire game's codebase at three in the + morning and look at every single callsite trying to find the one + errant call that's putting some global module in an unexpected + state. + +* **It encourages coupling.** The new coder on your team isn't + familiar with your game's beautiful decoupled architecture, but + he's just been given his first task: make boulders play sounds + when they fall onto the ground. You and I know we don't want the + physics code to be coupled to *audio* of all things, but he's just + trying to get his task done. Unfortunately for us, the instance of + our AudioPlayer is globally visible. So, one little `#include`, + and our new guy's now added a coupling between modules that will + cause maintenance headaches for years. + + If there hadn't been a global instance of the audio player, even + if he *did* `#include` the audio header, he still wouldn't be able + to get at what he needs. That difficulty would encourage him to + do things the right way. When you control access to instances, + you control coupling. + +* **It isn't concurrency-friendly.** The days of games running on a + simple single-threaded CPU are pretty much over. Code today must + at the very least work in a multi-threaded way even if it doesn't + take full advantage of concurrency. When you make something + global, you've created a chunk of memory that every thread can + see and poke at, without realizing what other threads may be + doing to it. It's all too easy to run into deadlocks or other + hideous synchronization issues. + +You'll notice that the Singleton pattern doesn't dodge *any* of those +bullets. A Singleton is still a global, just with a fancy name. + +### It Solves Two Problems, but You Probably Just Have One. + +That "and" in the Gang of Four's description of the pattern is a bit +odd. The two parts honestly don't seem that related. Ensuring a single +instance sounds pretty useful, but who says you want to let everyone +poke at it? Global access to an instance is definitely convenient, +but that's convenient even for classes where multiple instances really +wouldn't cause any problems. + +The latter of the two is almost always why programmers use the +Singleton pattern. Let's say your game has a VFX (visual effects) +engine for little bits of graphical dazzle like smoke, explosions, +and other flashes of light and color. A bunch of places will need to +touch that: your AI code may need to put on a light show when our +hero levels up, the physics code shoots out a shower of sparks when swords collide. We need a single point of access so that all of those +systems can get to our VFX one. + +So we reach for the Singleton pattern, but we get this other odd little rule comes along for the ride. All of the sudden, we can no +longer instantiate more than one VFX system, even though there's +nothing in its code that would be harmed by doing so. + +At first, it seems like a harmless addition. There probably is only +one VFX system in the game anyway, so who cares if it won't let you +make more. The problem is that what's right today may not be right +tomorrow. You may later decide to run separate VFX systems in parallel +so that you can allocate more memory to one for more important effects, and restrict the other for secondary effects. + +This is even more painful when the code you've arbitrarily forced to +only allow a single instance is in a library shared across multiple +teams. Now your library isn't only providing a service, it's also +dictating exactly how every client must use that service, regardless +of if that's the best way for each game. + +### Lazy Initialization is Risky. + +In the PC world of infinite virtual memory and loose performance +requirements, using lazy initialization is a pretty neat trick. It +doesn't translate so well to games, though. If creating your Singleton +audio system takes a few hundred milliseconds, initializing it will +cause visibly dropped frames and the game will stutter. You really +don't want to wait until the player is in the middle of a level and +causes the first sound to play when that happens. + +Likewise, games generally need to closely control how memory is laid +out in the heap to avoid memory fragmentation (see Object Pool for +more on fragmentation). If your Singleton allocates a chunk of heap +when it initializes, you probably want to know exactly when that's +going to occur. + +The get around this, most codebases I've seen implement the Singleton +pattern like this: + +^code 5 + + + +That solves the lazy initialization problem, but at the expense of +discarding everything about Singleton that *does* make it better than +a raw global variable. With the above, you can no longer use polymorphism, and the class must be constructible at static initialization time. All you've done is taken a global variable and +put a nice bow around it. + +## Where Do We Go From Here? + +If I've done my job, you'll think twice before you pull Singleton out +of your toolbox the next time you have a problem. But you still have +your problem. What tool *should* you pull out? In truth, sometimes +Singleton is the right choice, but based on what you're specifically +trying to address, I may have some other options for you. + +### See If You Need The Class at All + + + +Before we go about solving your problem, let's make sure we can' just +erase it completely. More often than I'd like, when I see someone +using Singleton, it's to implement a "manager": that nebulous class +that babysits other objects. I've seen codebases where it seems like +*every* class had a manager: Monster, MonsterManager, Particle, +ParticleManager, Sound, SoundManager. Sometimes, for variety, they'll +throw a "System" or "Engine" in there. + +While caretaker classes like that are sometimes useful, often they +just reflect unfamiliarity with OOP. Consider these two contrived +classes: + +^code 8 + +Maybe this example is a bit dumb, but I've seen plenty of code that +after scraping away the crusty details reduces down to something like +this. If you look at this code, it's natural to think, "Well, +BulletManager needs to be a Singleton." After all, anything that has a +Bullet will need the manager too, and how many instances of +BulletManager do you need? + +The answer is *zero*, actually. In OOP, objects are +supposed to take care of themselves. So the way to solve the "singleton" problem for our manager class is like this: + +^code 9 + +There we go. No manager, no problem. Of course, this won't work in +every case. There are plenty of times where you do need a class that +"manages" others. A common example is an Object Pool. But, just for my peace of +mind, if you find yourself creating a "manager" type class, take a +moment to see if you need it at all or if your objects can get a +promotion and manage themselves. + +### To Limit a Class to a Single Instance + +This is the first half, and only the first half of what Singleton +solves. If you're writing a class that wraps an external API, it can +be critical to ensure there's only a single instance of that wrapper. + +However, that doesn't mean it's your class's job to provide *access* +to that instance. It may be that the game wants to restrict access to +the single instance to certain areas of the code, or maybe even make +it entirely private to a single class (they may be wrapping your +wrapper in one of their own). + +There are a couple of ways to accomplish this. Here's one: + +^code 6 + +This class allows anyone to construct it, but will fail if you try to +construct more than one instance. This sidesteps the problems with +Singleton's lazy initialization, and also lets the calling code control where the instance lives and when it is created. It also lets +the calling code destroy the instance and then create another one +later. + +The downside, and it may be a big one for you, is that the +check to prevent multiple instantiation is only done at *runtime*. The +Singleton pattern, in contrast, guarantees a single instance at +compile time, by the very nature of the class's structure. + +We can get back our compile-time guarantee of single instantation by +going back to Singleton but splitting it into two classes: + +^code 7 + +Our FileSystem class uses a private constructor to prevent arbitrary +code from creating instances of itself, but it doesn't actually create +any instances of its own. Instead, it declares a friend class, and +grants that special class the freedom to instantiate it. + +The problem here, of course, is the our FileSystem class is still +coupled to the class that has an instance of it, but at least we're +no longer having the FileSystem grant access to an instance of itself +to *everyone*. In our example where we want to wrap the FileSystem +class in another wrapper, it encapsulates the one instance of +FileSystem completely, so that no other code can get at the unwrapped +FileSystem. + +### To Provide Convenient Access to An Instance + +This is the reason we reach for Singletons, they make it easy to get +our hands on an object we need to use in a lot of different places. +That ease comes at a cost, though: it's also easy to get our hands on +the object in places where we *don't* it being used. + +In general, you want things to be as narrowly scoped as possible while +still getting the job done. The smaller the scope an object has, the +fewer places you need to keep in your head while you're working on it. +So, before we take the nuclear option of Singleton and give an object +*global* scope, let's see if there are any other smaller scopes that +will still reach all the places where we need our object. + +Before we get into specifics, we need an example. Our Singleton +candidate will be a class for playing sounds. Since lots of places in +a game need to cause sound to play, we're going to need to make it +easy to get to. Here's the class: + +^code 10 + +The question now is, how can we take advantage of the architecture of +the rest our game to give access to that without making it completely global? Consider a few different cases: + +* **If every place that needs it is derived from the same class** + then we can let the base class hold the instance and give the + derived classes access to it through a protected member. Like so: + + ^code 11 + + If this looks like it could work for you, check out the + Sandbox Method + Pattern. + +* **If every place that needs it implements the same interface** + then you can simply pass the instance in to the methods in that + interface: + + ^code 12 + + This is similar to the previous case, but is useful when the base + class is a pure interface and you don't want it to hold any state. + If this fits your design, see Context Parameter. + +* Finally, **if it's needed in classes that aren't related, but + those classes only care that the instance implements a certain + interface** we can use a pattern called Service. Briefly, it looks like: + + ^code 13 + + The `IAudioPlayer` interface abstracts away the concrete + `AudioPlayer` class. Any code that needs to play audio can get an + instance of it through the static `Provider` class. Unlike + Singleton which instantiates *itself*, the Provider class requires + some external code somewhere to actually register the intance with + it. + + This is still a bit undesirable because the Provider makes our + audio player globally available, but it at least decouples the + calling code from the concrete audio player class, and it lets + our code control when the concrete audio player class is + instantiated. If I can't reduce scope by actually passing around a + reference to the instance I need, I reach for this pattern before + I go for Singleton. + +## What's Left? + + + +The question remains, where *should* you use the actual full Singleton +pattern? In my experience, the answer is *never* in a game. There are +other simpler patterns for guaranteeing single instantiation (often +simply using a static class is effective) and providing convenient +access to an instance. Many of those patterns are described here in +Section 3. The remaining feature of Singleton, lazy initialization, +is useful for PC software, but not something I've ever found myself +wanting in a game codebase. diff --git a/draft/first draft/spatial-partition.markdown b/draft/first draft/spatial-partition.markdown new file mode 100644 index 0000000..bd75493 --- /dev/null +++ b/draft/first draft/spatial-partition.markdown @@ -0,0 +1,388 @@ +^title Spatial Partition +^section Optimizing Patterns + +## Intent + +*Quickly find objects near a certain location by storing them in a data +structure organized by position.* + +## Motivation + +We play games in part because they let us visit other worlds different from our +own. But those worlds typically aren't *so* different. They often share the same +basic physics and tangibility of our world. This is why they can feel so real. + +One simple facet of that that we'll be focusing on here is *location*. Game +worlds usually have a sense of *space* and objects are somewhere in that space. +This manifests itself in a bunch of ways. The obvious one is physics: objects +move, collide, and interact. But the audio engine may take into account where +sound sources are relative to the player. Online chat may be restricted to other +players close to yourself. + +Space, and *spatialness* permeates your game engine. Often, it boils down to +needing answer to the question, "what objects are near this location". If you +find yourself asking this frequently enough each frame, and it can start to be +a performance bottleneck for your game. + +### Units on the field of battle + +Say we're making a real-time strategy game. Opposing armies with hundreds of units will clash together on the field of battle. Warriors need to know who is nearby to know who to avoid and who to swing their blade at. + +The naïve way to determine this is to look at every pair of units and see how close they are to each other: + +^code pairwise + +Here we have a doubly-nested loop where each loop is walking all of the units on the battlefield. That means the number of pairwise tests we have to perform each frame increases with the *square* of the number of units. Each additional unit you add has to be compared to *all* of the previous ones. With a large number of units, that can spiral out of control. + + + +### Drawing battlelines + +Before we see how this pattern tackles our big optimization problem, let's see how we'd solve a smaller one first. Imagine if our battlefield was simplified such that any unit could only be in one of a few different locations. Instead of a 2D battle*field*, imagine it's a 1D battle*line*. Not only that, but only *integer* positions are allowed. A unit's position is just an int within some known small range. + +Again, we're trying to find which units are battling, which means which units have the same position. But now that there are only a few positions in question, we can adjust our data structure to make that explicit. Instead of a single array of units, we'll have an array of units *at each position*: + +^code pigeonhole-array + + + +Since we don't have a single array of units, we'll have to look at each position to see who is fighting: + +^code pigeonhole-outer + +Then within that position, we look at each pair of units: + +^code pigeonhole + +This effectively means that instead of a *doubly*-nested loop, now it's *triply*-nested. It seems we've made things worse! + +There's a big difference, though: each of those inner arrays will be *much* shorter. In fact, with this artificially simple problem we've set up for ourselves, each array will *only* contain units that actually are in combat since they will have the exact same position. + +For example, consider a battleline with 100 distinct positions and 200 units randomly distributed on them. Our first brute-force solution will have to compare 199,000 pairs of units to find around 200 actual hits. + +With a separate array for each position, we don't have to look at *any* pairs of units that aren't actually hitting each other. Instead of 199,000 "do you have the same position?" checks, we go straight to the 200 known hits, a factor of 100 improvement. + +The lesson is pretty obvious: if you store your objects in a data structure organized by their location, you can find them much more quickly. Our artificially sample program with just a couple of fixed positions makes that trivial to do. This pattern is about applying the same technique to harder problems: ones where objects are in two or three dimensions, where their position can vary continuously, or where you might not even know the bounds they have to fit within. + + + +## The Pattern + +A set of **objects** each have a **position in space**. Store them in a **spatial data structure** that organizes them by that position. This data structure lets you **efficiently query for objects at or near a location**. When an object's position changes, **update the spatial data structure** so that it can continue to find it. + +## When to Use It + +This is a very common pattern for storing both live, moving game objects and also often the more static art and geometry of the level. Sophisticated games will often have multiple different spatial partitions for different kinds of content. + +However, like many patterns, it does have some complexity overheard. Advanced spatial partitioning techniques require real engineering work to get right. Don't pull this out of the toolbox unless you know its worth the effort. + +The basic requirements for this pattern are that you have a set of objects that each have some kind of position and that you frequently need to query that set to find objects near each other or near some location. + +## Keep in Mind + +Spatial partitions exists mainly to knock an *O(n²)* query down to something more manageable. The *more* objects you have, the more valuable that becomes. Conversely, if your *n* is small enough, it may not be worth the bother. + +Since this pattern involves organizing objects by their position, objects that *change* their position are harder to deal with. You'll have to reorganize the data structure to keep track of the object at its new location, and that adds code complexity *and* burns CPU cycles. Make sure the trade-off is worth it. + + + +Spatial partitions also take up a chunk of additional memory for the bookkeeping data structures. Like many optimizations, they trade memory for speed. If memory is tight, that may be a losing proposition. + +## Sample Code + +Like most patterns, spatial partitions come in a lot of different flavors. Unlike some other patterns, though, these specific flavors are quite well-documented and specified. It's a lot easier to get a paper published that says "this data structure improves your performance by a factor of four and here's a proof" than "this kinda makes your code a bit easier to maintain, I think, not that I have any numbers to back it up." + +A number of spatial partitioning strategies have been codified. *[BSPs][]*, *[k-d trees][]*, and *[quadtrees][]* (and *octtrees*, their 3D cousins) are the most common ones in games but there are more exotic structures floating around in academia. Because what I care about is the general *idea* of the pattern, I'm going to show you the simplest possible spatial partition that's still useful for real-world games: *a fixed grid.* + +[bsps]: http://en.wikipedia.org/wiki/Binary_space_partitioning +[k-d trees]: http://en.wikipedia.org/wiki/K-d_tree +[quadtrees]: http://en.wikipedia.org/wiki/Quad_tree + +### A sheet of graph paper + +Here's the basic idea: imagine the entire field of battle. Now superimpose a grid of squares onto it, like a sheet of graph paper. We pick how big these squares are and then tile them across the entire game area. + +Instead of a single array of units, we store them in the cells of this grid. For each cell, we store the list of units whose position falls within that cell's bounding box. When we go to handle combat, we only need to consider units within the same cell. Instead of comparing every unit in the game with every other one, we've *partitioned* the battlefield into a bunch of smaller mini-battlefields, each with a much smaller number of units to consider. (In fact, many will be empty.) + +This is so simple you'd probably invent it yourself if you'd never heard of it, but even so it's still surprisingly effective. Enough talk, time for code! + + + +### A grid of linked units + +First let's get some prep work. Here's our basic unit class: + +^code unit-simple + +Each unit has a position (in 2D). And it has a pointer to the `Grid` that it lives on. This will be a bit like our array of positions in the earlier trivial example. We make `Grid` a `friend` class because, as we'll see, when a unit's position changes, it has to do an intricate dance with the grid to make sure everything is updated correctly. + +Here's a sketch of the grid: + +^code grid-simple + +In the earlier example, we used a simple array to store the units at each position. We're going to do something a bit more flexible this time. Here, each "cell" is just a pointer to a unit. This works because we extend `Unit` with `next` and `prev` pointers: + +^code unit-linked + +This organize units in a doubly-linked list, instead of a array. Each cell in the grid then just points to the first unit in the linked list of units within that cell and each unit has pointers to the ones before and after it in the list. We'll see why soon. + + + +### Entering the field of battle + +The first thing we need to do is make sure new units are actually placed into the grid when they are created. We'll make unit handle this in its constructor: + +^code unit-ctor + +This `add()` method is defined like so: + +^code add + +It's a little finicky, like linked list code always is, but the basic idea is pretty simple. We find the cell the unit is sitting in and then add it to the front of that list. If there is already a list of units there, link it in after the new unit. + +### A clash of swords + +Once all of the units are nestled in their cells, we can let them start hacking at each other. With this new grid, the main method for handling combat look like this: + +^code grid-melee + +Similar to the earlier example, it just walks each cell and then calls `handleCell` on it. As you can see, we really have partitioned the battlefield into little isolated skirmishes. Each cell then handles its combat like so: + +^code handle-cell + +Aside from the pointer shenanigans to deal with walking a linked list, you'll note that this is exactly like our original naïve method for handling combat: it compares each pairs of units to see if they're in the same position. + +The only difference is that we no longer have to compare *all* of the units to each other. That's where all of the performance benefit comes from. + +### Charging forward + +We've solved our performance problem, but we've created a new problem in its stead. Units now are confined to their cell. If you move a unit outside of the cell that contains it, units in the old cells won't see it anymore, but nor will anyone else. Our battlefield is a little *too* partitioned. + +To fix that, we'll need to do a little work each time a unit moves. If it crosses a cell's boundary lines, we need to remove it from that cell and add it to the new one. First, we'll give `Unit` a method for changing its position: + +^code unit-move + +Presumably, this gets called by the AI code for computer-controlled units and by the user input code for the player's. All it does is hand-off control to the grid, which then does: + +^code grid-move + +That's a mouthful of code, but it's pretty straightforward. The first bit checks to see if we've crossed a cell boundary at all. If not, we can just update the unit's position and we're done. There's nothing else to do. + +If it *has* left its current cell, we remove it from that cell's linked list and then add it back to the grid. Just like with a new unit, that will then insert it in the linked list for the cell that contains its new position. + +This is why we're using a doubly-linked list here: we can very quickly add and remove units from lists just by setting a few pointers. With lots of units moving around each time, that can be important. + +### At arms' length + +This seems pretty simple, but I have cheated in one way. In the example, I've been showing, units only interact when they have the *exact same* position. That's true for checkers and chess, but less true for more realistic games. Those usually have attack *distances* to take into account. + +This pattern still works fine. Instead of just checking for an exact location match, you'll do something more like: + +^code handle-distance + +Or probably something more advanced and based on your game's mechanics. When your game works this way, there's a corner case you need to consider: units in different cells may still be close enough to interact. + + +--------+--------+ + | _| | + | / |\ | + | | A|B| | + | \_|/ | + | | | + +--------+--------+ + +Here, B is within A's attack radius even through their centerpoints are in different cells. To handle this, you will need to compare not just units in the same cell, but in neighboring cells too. To do this, first we'll split the inner loop out of `handleCell()`: + +^code handle-unit + +Now we have a function that will take a single unit and a list of other units and see if there are any hits. Then we'll make `handleCell()` use that: + +^code handle-cell-unit + +Note also that we pass in the coordinates of the cell now and not just its unit list. Right now, this doesn't do anything different than the previous example, but we'll expand it slightly: + +^code handle-neighbor + +Those additional `handleUnit()` calls look for hits between the current unit and four of the eight neighboring cells: + + + +If any unit in those neighboring cells is close enough to the edge to be within the unit's attack radius, it will find the hit. We only look at *half* of the neighbors for the same reason that the inner loop starts *after* the current unit: to avoid comparing each pair of units twice. + +Consider what would happen if we did check all eight neighboring cells. Let's say we have two units in adjacent cells, close enough to hit each other, like this: + + +---+---+ + | A|B | + +---+---+ + +If, for each unit, we looked at all eight cells surrounding it, here's what would happen: + +1. When we are finding hits for A, we would look at its neighbor on the right and find B. So we'd register an attack between A and B. +2. Then, when we are finding hits for B, we would look at its neighbor on the *left* and find A. So we'd register a *second* attack between A and B. + +Only looking at half of the neighboring cells fixes that. *Which* half doesn't matter at all. + +There's another corner case you may need to consider too. Here, I'm assuming the maximum attack distance is smaller than a cell. If you have very small cells and large attack distances, you may need to scan a bunch of neighboring cells, several rows out. + +## Design Decisions + +### Is the partition hierarchical or flat? + +You can roughly organize spatial partition strategies into two buckets. Some break up space into a single collection of partitions, like the cells in our grid example. Others partition recursively: each partition may itself be broken into sub-partitions. + +Hierarchical spatial partitions work by breaking the entire space into just a couple (usually two, four or eight) of regions. Then, if a region contains a large enough number of objects, it's recursively subdivided. This process continues until any given leaf partition only has a certain maximum number of objects in it. + +Most of the big time serious spatial partition data structures you read about like BSPs and quadtrees are hierarchical, but simpler partitions like grids can be useful too. + +* **If it's a single level of partitioning:** + + * *It's simpler.* Flat data structures are easier to reason about, and simpler to implement. + + + + * *It can be faster to update when objects change their position.* When an object moves, the data structure needs to be updated to find the object in its new location. With a hierarchical spatial partition, this can mean adjusting several layers of the hierarchy. If lots of objects are moving all the time, that can be a noticeable time sink. + +* **If it's hierarchical:** + + * *It handles empty space more efficiently.* One reason hierarchical spatial partitions are used is that they handle large empty spaces more efficiently. Imagine in our earlier example if one whole side of the battlefield was empty. With our simple grid, that would lead to a large number of empty cells. We'd still walk all of those cells each frame. + + Since hierarchical space partitions don't subdivide sparse regions, a large empty space will remain a single partition. Instead of lots of little partitions to walk, there would just be the one big empty one. + + * *It handles densely populated areas more efficiently.* This is the other side of the coin: if you have a bunch of objects all clumped together, a non-hierarchical partition can be ineffective. You'll end up with one partition that has so many objects in it, you may as well not be partitioning at all. A hierarchical partition will usually adaptively subdivide that into smaller partitions and get you back to having only a few objects to consider at a time. + +### Does the partitioning depend on the set of objects? + +In our simple grid example, the size of the grid cells was fixed beforehand and then we just slotted units into them. Other partitioning schemes are more adaptable: they will pick the partition boundaries based on the actual set of objects and where they are in the world. + +The goal is have a *balanced* partitioning, where each subset of the world has roughly the same number of objects. Keeping the partitions balanced helps performance. Consider in our grid example if all of the units were clustered in one corner of the battlefield. They'd all be in the same cell, and our code for finding attacks would regress right back to the original *O(n²)* problem that we were trying to solve. + +* **If the partitioning is object-independent:** + + Our grid example is one partition that works this way: the cell size is selected up front. + + * *The space can be filled incrementally.* Adding new objects generally just means finding the right partition to add it to. You can do this one at a time without any real performance issues. If the partitions are based on the set of objects, you don't even know how things are partitioned until you have a set of objects to play with. In those cases, it's more efficient to be able to partition the entire set of objects at once. + + * *Objects can be moved quickly.* With our grid example, moving a unit just means removing it from one cell and adding it to another. If the partition boundaries themselves change based on the set of objects, then moving one can cause a boundary to move, which can in turn cause lots of other objects to need to be moved to different partitions if the boundary has moved past them. + + This is directly analagous to sorted binary search trees like red-black trees or AVL trees: when you add a single item, you may end up needing to resort the entire tree and shuffle a number of nodes around. + + * *The partitions can be imbalanced.* Of course the downside of this rigidity is that you have less control over your partitions being imbalanced. If the set of objects starts clustering together, you can degrade performance. + +* **If the partitioning adapts to the set of objects:** + + Spatial partitions like BSPs and k-d trees split the world recursively so that each half contains about the same number of objects. This means that choosing the planes that you divide along requires counting how many objects are on each side. + + Bounding volume hierarchies are another spatial partition that optimize for the specific set of objects in the world. + + * *You can ensure the partitions are balanced.* This gives not just good performance, but *consistent* performance: if each partition has the same number of objects, you ensure that all queries in the world will take about the same amount of time. With games that need to maintain a consistent frame-rate, sometimes this consistency is more important than being as fast as possible in some cases. + +* **If the partitioning is object-independent, but *hierarchy* is object-dependent:** + + One spatial partition deserves special mention because it has some of the best characteristics of both fixed partitions and adaptable ones: quadtrees. A quadtree partitions the world into four cells: + + +-------+-------+ + | * * | | + | | | + | * | * | + +-------+-------+ + | | *| + | | * | + | | | + +-------+-------+ + + Then, for each of those squares, if the number of objects in it is large enough, it is subdivided: + + +-------+-------+ + | * |* | | + |---+---| | + | | * | * | + +-------+-------+ + | | *| + | | * | + | | | + +-------+-------+ + + This process continues recursively until every square has fewer than some maximum number of objects. This means that the partitions don't *move* based on the set of objects, but the number of *subdivisions* is object-dependent. + + * *The space can be filled incrementally.* Adding a new object means just finding the right square and adding it. If that bumps that square above the maximum count, it gets subdivided. The other objects in that square get pushed down into the new smaller squares. This requires a little work, but it's a *fixed* amount of effort: the number of objects you have to push into the smaller squares will always be less than that maximum object count. + + Removing objects is equally simple. You remove the object from its square and if the parent square's total count is now below the threshhold, you can collapse those subdivisions. + + * *Objects can be moved quickly.* This, of course, follows from the above. "Moving" an object is just an add and a remove and both of those are pretty quick with quadtrees. + + * *The partitions are balanced.* Since any given square will have less than some fixed maximum number of objects, comparing objects withing a square can never go pathological if a bunch of objects are clustered together. + +### Are objects only stored in the partition? + +You can treat your spatial partition as *the* place where the objects in your game live, or you can consider it just a secondary cache to make look-up faster, while there is also a second primary collection that holds the objects. + +* **If it is the only place objects are stored:** + + * *It avoids the memory overhead and complexity of two collections.* Of course, it's always cheaper to store something once instead of twice. Also, if you have two collections, you have to make sure to keep them in sync. Every time an object is created or destroyed, it has to be added or removed from both. + +* **If there is another collection for the objects:** + + * *Traversing all objects is faster.* If the objects in question are "live" and have some processing they need to do, you may find yourself frequently needing to visit every object regardless of its location. Imagine if, in our earlier example, most of the cells were empty. Having to walk the full grid of them just to find the non-empty ones can be a waste of time. + + A second collection that just stores the objects gives you a way to walk all of the objects. You effectively have two data structures, one optimized for each access pattern. + +## See Also + +* I've tried to not discuss specific spatial partitioning structures in detail here to keep the chapter high-level (and not too long!) but your next step from here should be to learn a few of the common ones. Despite their scary names, they are all surprisingly straightforward. The common ones are: + + * [Grid](http://en.wikipedia.org/wiki/Grid_(spatial_index)) + * [Quadtree](http://en.wikipedia.org/wiki/Quad_tree) + * [BSP](http://en.wikipedia.org/wiki/Binary_space_partitioning) + * [k-d tree](http://en.wikipedia.org/wiki/Kd-tree) + * [Bounding volume hierarchy](http://en.wikipedia.org/wiki/Bounding_volume_hierarchy) + +* Each of these spatial data structures, is basically extending an existing well-known data structure from 1D into more dimensions. Knowing the performance characteristics of their 1D cousins will help you tell if they are a good fit for your problem: + + * A grid is effectively a [bucket sort](http://en.wikipedia.org/wiki/Bucket_sort) in multiple dimensions. + * BSPs, k-d trees, and bounding volume hierarchies are [binary search trees](http://en.wikipedia.org/wiki/Binary_search_tree). + * Quadtrees and octrees are [tries](http://en.wikipedia.org/wiki/Trie). diff --git a/draft/first draft/update-method.markdown b/draft/first draft/update-method.markdown new file mode 100644 index 0000000..95ff324 --- /dev/null +++ b/draft/first draft/update-method.markdown @@ -0,0 +1,258 @@ +^title Update Method +^section Sequencing Patterns + +## Intent + +*Allow a collection of entities to simulate concurrently but independently from each other by giving each entity the opportunity to update itself once per frame.* + +## Motivation + +The player's mighty warrior is on a quest to steal the glorious jewels from where they rest on the bones of the long-dead sorcerer-queen. He walks to the entrance of her magnificent crypt to discover... nothing. No cursed statues shooting lightning at him. No undead warriors guarding the entrance. He can just walk right in that grab the loot. That won't do. + +This crypt needs some protection: some enemies our brave hero can grapple with. First up, we want a re-animated skeleton guard to patrol back and forth in front of the door. (If the sorcerer-queen wanted more intelligent behavior, she should have re-animated something that still had brains.) + +Our actual game deals with user input, rendering, and maintaining a stable framerate. That means all of the game code is nestled within a comfy little game loop. If we ignore that for the moment and imagine the simplest possible code to move our skeleton back and forth, we'd have something like: + +^code just-patrol + +The problem here, of course, is that the skeleton moves back and forth, but the player never sees it. That loop doesn't exit, so this just locks the game. Not exactly a fun gameplay experience. What we actually want is for the skeleton to move one step *each frame*. We'll have to remove those loops and rely on the outer game loop for iteration. That ensures the game will keep playing while the guard is making his rounds. Like: + +^code patrol-in-loop + +The main thing to notice is that the code got more complex. Patrolling left and right used to be two separate simple `for` loops. We kept track of which direction the skeleton was moving implicitly by where in the code we were currently executing. Now that we have to unwind out to the main game loop and then resume where we left off each frame, we have to track that explicitly using that `patrollingLeft` variable. + +But, aside from that, this more or less works, so we keep going. This skeleton is a bit too stupid to give the hero much of a challenge, so the next thing we want to do is add a couple of enchanted statues. These will fire bolts of lightning at the hero every so often. Continuing along our, "what's the simplest way to code this" style, we end up with: + +^code statues + +I'm pretty sure you can tell this isn't ending up like something we'd enjoy maintaining. We've got an increasingly large pile of variables and imperative code all stuffed in the game loop each handling one specific entity in the game. To get a bunch of entities all alive and moving at the same time, we've mushed their code together. + +The pattern we use to fix this is so simple, you probably have it in mind now just from looking at that nasty code. What we want is entities in the game to encapsulate their behavior. This keeps the game loop uncluttered and make it easy to add and remove entities. + +What we need is an *abstraction layer*, and we create that by defining an abstract `update()` method on the entity class. The game loop maintains a collection of entities, but it doesn't know their concrete types. All it knows is that they have an `update()` method. Since each entity implements `update()` itself, the behavior for entities are nicely encapsulated both from the game loop, and from each other. + +Each frame, it walks the collection and calls `update()` on each entity. This gives each entity a slice of time to behave each frame. By calling it on all entities every frame, they all behave simultaneously. + +The game loop just has a collection of entities, so adding and removing them from the level is easy: just add and remove them from the collection. Nothing is hardcoded anymore, and we can even populate the level using some kind of data file, which is exactly what our level designers want. + +## Pattern + +The **game world** maintains a **collection of objects**. Each object implements an **update method** that simulates one frame of behavior. Each frame, the game updates every entity in the collection. + +## When to Use It + +This pattern is the jelly to Game Loop's peanut butter. Almost every realtime game out there that has living actors that the player interacts with uses this pattern is some form or other. If the game has space marines, dragons, martians, ghosts, or athletes, there's a good chance it uses this pattern. + +However, if the game is more abstract and the moving pieces are less like living characters and more like pieces on a boardgame, this pattern is often the *wrong* choice. In a game like chess, you don't need to simulate all of the pieces concurrently, and you probably don't need to tell the pawns to update themselves each frame. + +Even if your game features entities that do have complex, real-time behavior, if the entities needs to be very highly coordinated with each other (think aliens flying in tight formations in Galaga), this pattern may cut against the grain of your problem. Update methods work best when: + +* Your game has a number of entities or systems that need to run concurrently. +* Each one has its own behavior that's relatively independent of the others. +* They need to be simulated over time. + +## Keep in Mind + +This pattern is pretty simple, so there aren't a lot of hidden surprises in its dark corners. Still, even a simple method has a couple of ramifications. + +### Splitting behavior into one frame slices makes code more complex + +The first code sample I showed for the patrolling skeleton guard used its own looping directly and ran the entire patrol without returning. Then, in the second sample, that was changed to return to the outer game loop each frame. + +Taking a behavior that spans multiple frames, like patrolling back and forth and flattening it out into a piece of code that exits at the end of each frame adds a good bit of complexity. You have to move state stored in local variables into something more permanent like fields on some object. + +That change is almost always necessary for the game to handle user input and all of the other fun stuff that the game loop takes care of, so the first example wasn't very practical in a real game. I showed it there, just so you can see the contrast. It's worth keeping in mind there's this sunk complexity cost. + + + +### You have to store state to be able to resume where you left off each frame + +In the first code sample, we didn't have any variables to indicate whether the guard was moving left or right. That was implicit based on which code was currently executing. When we transformed this to an one-frame-at-a-time form, we had to create a `movingLeft` variable to track that state. Since we return out of the code each frame, the execution position is lost each frame, and we need to explicitly track enough information to restore that on the next frame. + +One pattern that often helps when doing this is the State pattern. The fact that entity behavior is split into code that executes incrementally over many frames is one reason that state machines and similar patterns are so common for entity AI. + +### Entities all simulate each frame but are not truly concurrent + +The basic pattern used by most games is that each frame, they loop over every entity in the world and tell each one to update one frame. The code for updating those entities often accesses parts of the world's state, including other entities in the world. + +This means that the *order* that the entities are updated *within* a frame is actually meaningful. If A updates before B, then when B updates, it will see A's *new* position, not its previous one. Even though from the player's perspective everything is moving at the same time, the core of the game is still turn-based. It's just that a "turn" is one frame long. + + + +The fact that the game is still technically sequential is mostly a good thing. It certainly makes the code simpler and requires less memory because you don't have to store both the previous and current state for each entity. + +It's a boon as far as the game logic is concerned too. Allowing entities to truly update independently from each other leads you to some unpleasant semantic corners. Imagine a game of chess where black and white moved at the same time. They both try to make a move that places a piece in the same currently empty square. How should this be resolved? + +Keeping things sequential makes that much simpler: the world is in a consistent state *before* each entity takes its turn, and is consistent *after* its turn too. Instead of having to determine how a set of simultaneous moves are reconciled together, the game state just updates incrementally. + +### Be careful modifying the entity list while updating + +When you're using this pattern, a lot of the game's behavior ends up nestled in these update methods. That includes behavior that adds or removes entities from the game. + +For example, a monster summons another of its kin to help. With new entities, you can usually just add them to the end of the list without too much trouble. You'll keep iterating over that list and eventually get to the new entity at the end and update it too. But that does mean that the new entity gets a chance to act during the frame that it was spawned, before the player has has a chance to even see it. + +If you don't want that to happen, one simple fix is to store the number of entities in the list at the beginning of the update loop and only update that many before stopping: + +^code skip-added + +Here, `entities` is an array of the entities in the game, and `numEntities` keeps track of how many are in it. When new entities are added, that gets incremented. We cache its value in `numEntitiesThisTurn` at the beginning of the loop so that the iteration stops before we get to any new entities added in the middle of the current update loop. They'll be updated the next frame. + +A slightly hairier problem is when entities are *removed* while iterating. You vanquish some foul beast and now it needs to get yanked out of the entity list. If it happens to be right after the current entity you're updating in the list, you can accidentally skip an entity: + +^code skip-removed + +This simple loop increments the index of entity being updated each iteration. But imagine the entity list array looks like: + + 0. Foul Beast + 1. Hero + 2. Hapless Peasant + +We're updating the hero, so `i` is 1. He slays the foul beast so it gets removed from the array. The hero shifts up to 0, and the hapless peasant shifts up to index 1. After updating the hero, `i` is incremented to 2, skipping right over the peasant. + +This can do even nastier things if you're using something like a linked list for your entities and you happen to remove the entity currently being updated. One option is to just be careful when you remove entities and update any iteration variables to take the removal into account. + +Another option is to defer actual removals until after you're done walking the list. Just mark the entity as "dead" but leave it there. If it comes up for being updated, skip it. Then, when you've updated everything, walk the list again but *only* to remove the dead entities. + +## Sample Code + +This pattern is one of those simple ones where the sample code almost belabors the point. That doesn't mean the pattern isn't useful. It's so useful in part *because* it's simple: it's a clean solution to a problem without a lot of ornamentation. + +But just to keep things concrete, let's walk through a basic implementations. We'll start with our entity class: + +^code entity-class + +I stuck a few things in there, but just the bare minimum we'll need later. Presumably in real code there'd be lots of other stuff in there too like, graphics, physics, etc. We'll just ignore that for now. The important bit for this pattern is that it has a *virtual* `update()` method. + +The game maintains a collection of these entities. In our sample, we'll put that in a class representing the game world: + + + +^code game-world + + + +Now that everything is set up, the game implements the pattern by updating each entity every frame: + + + +^code game-loop + + + +### Subclassing entities?! + +Now that we've got the basic skeleton up and running, we can start using the pattern to let us define some entity behaviors. Before I get started, though, I need to make a formal apology. In this example, I've hung the abstract `update()` method directly on the `Entity` class. That means implementing `update()` requires subclassing `Entity`. + +Right now, the popular way to define your game entities is using the Component pattern. Using that, `update()` would be on the components themselves instead of entity. That lets you avoid creating complicated class hierarchies of entities to define behaviors. Instead, you can just mix and match components. + +Component systems are popular for a reason: they're much more flexible and maintainable than big rigid class hierarchies. But this also means that using inheritance here is now really *un*popular and some people think it's a moral crime for me to even use it in an example. + +But this chapter isn't about components. It's about `update()` methods, and the simplest way I can show them, with as few moving parts as possible, is by just putting that method right on `Entity` and making a few subclasses. + + + +### Defining entities + +OK, back to the task at hand. Our original motivation was to be able to define a patrolling skeleton guard and some lightning-bolt-unleashing magical statues. Let's start with the former. To define the patrolling behavior, now we just make a new kind of entity that implements `update()` appropriately: + +^code skeleton + +As you can see, we pretty much just cut that chunk of code from the game loop earlier in the chapter and pasted it into `Skeleton`'s `update()` method. The one minor difference is that `patrollingLeft_` has been made into a field instead of a local variable. That way its value sticks around between calls to `update()`. + +Let's do this again with the statue: + +^code statue + +Again, most of the change is just moving code out of the game loop and into the class and renaming some stuff. In this case, though, we've actually made things simpler. In the original nasty imperative code, there were separate local variables for each statue's current number of frames since it last fired, and also for each statue's delay between firing. + +Now that those have been moved into the `Statue` class itself, each statue takes care of that state on its own. You can create as many statues as you want and each one will have its own little timer and rate of fire independently of all of the others. And that's really the motivation behind this pattern: it's now much easier to add new entities to the game world because each one brings along everything it needs to take care of itself. + +This pattern lets us separate out *populating* the game world from *implementing* it. Each entity now includes its own behavior and the game engine itself doesn't have any explicit code driving the entities in the world. This means all you need to do is instantiate and entity and add it to the world's `entities_` collection, and the entity will take it from there. + +This in turn gives us the flexibility to populate the world using something like a separate data file or level editor. That's a huge win for productivity because it means non-programmers on the team can build the world and iterate on it without needing a coder in the loop. The "separation of concerns" in the code, where each entity contains its own behavior, can translate to a separation of concerns on your *team* where some people define how entities behave while others define which entities are present in each level. + +## Design Decisions + +With a simple pattern like this, there isn't too much variation, but there's still a couple of knobs you can turn. + +### What class does `update()` method live on? + +The most obvious and most important decision you'll make is what class to put `update()` on. + +* **The entity class** + + This is the simplest option. If you already have an entity class, this doesn't require bringing any addition classes into play. This may work if you don't have too many kinds of entities, but the industry is generally moving away from this. + + This is because you have to subclass the entity class to define new behavior. That can be brittle and painful if you have a large number of different kinds of behavior. The entity class will often contain lots of other code besides behavior: physics, graphics, etc. You may want to subclass to vary those instead (for example, to distinguish between visible and invisible entities, or for the different kinds of physical bodies). If you need to vary entities along multiple axes simultaneously, using subclassing to express doesn't work well. + +* **The component class** + + If you're already using the Component pattern, this is a no-brainer. Your +components will invariably need to be updated each frame, so you'll pretty much +have to do this. + + It lets each component update itself independently. In the same way that the update pattern in general lets you decouple game entities from each other in the game world, this lets you decouple parts of a single entity from each other. Rendering, physics, and AI can all take care of themselves. + +* **A delegate class** + + There are other patterns that involve delegating part of a class's behavior to another object. The State pattern does this so that you can change the entity's behavior by changing the object its delegating to. The Type Object pattern does this so that you can share behavior across a bunch of entities of the same "kind". + + If you're using one of those patterns, it's natural to put `update()` on that delegated class. In that case, you may still have the `update()` method on `Entity`, but it will just forward to the delegate object. Something like: + + ^code forward + + Doing this lets you define new behavior by changing out the delegated object. Like using components, it gives you the flexibility to change entity behavior without having to define an entirely new entity class. + +### How are dormant objects handled? + +You often have a number of objects in the world that for whatever reason don't need to be updated sometimes. It could be dead, or hidden, or disabled. If a large number of objects are in this state, it can be a waste of CPU cycles to walk over them each frame only to do nothing. + +Another option is to maintain a separate collection of just the "live" objects that do need updating. When an object is disabled, it's removed from the collection. When it's re-enabled, it gets added back. This way, you only walk over items that actually have real work do to. + +* **If you use a single collection containing inactive objects:** + + * *You waste CPU cycles*. For inactive objects, you'll end up either checking some "am I enabled" flag or calling a method that does nothing. + +* **If you use a separate collection of only active objects:** + + * *You use extra memory to maintain the second collection.* There's still usually another master collection of all entities for cases where you need them all. In that case, this collection is overhead from a memory perspective. When speed is tighter than memory (which it often is), this can be a worthwhile trade-off. + + Another option to mitigate this is to have two collections, but have the other collection only contain the *inactive* entities instead of all of them. + + * *You have to keep the collections in sync.* When objects are created or completely destroyed (and not just made temporarily inactive), you have to remember to modify both the master collection and this one. + +The metric that should guide your approach here is how many inactive objects you tend to have. The more you have, the more useful it is to have a separate collection that avoids them during your core game loop. + +## See Also + +* This pattern is part of a trinity with Game Loop and Component that often form the nucleus of the game engine. + +* Microsoft's XNA platform uses this pattern both in the [`Game`](http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.game.update.aspx) and [`GameComponent`](http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.gamecomponent.update.aspx) classes. + +* The [Quintus](http://html5quintus.com/) JavaScript game engine uses this pattern on its main [`Sprite`](http://html5quintus.com/guide/sprites.md) class. \ No newline at end of file diff --git a/draft/outline/context-parameter.markdown b/draft/outline/context-parameter.markdown new file mode 100644 index 0000000..f37f1b3 --- /dev/null +++ b/draft/outline/context-parameter.markdown @@ -0,0 +1,47 @@ +^title Context Parameter +^theme Behaving +^outline + +## Intent +*Define behavior in terms of operations on a given context object +passed to the method.* + +## Motivation +Each entity defines a Render() function to draw the entity. In a +naïve implementation, would call low-level render API directly. +But that couples entity code to entire render API, some of which may +be too low-level. + +Worse, in order to render, need to know right state. For example, +local transform of entity, camera position, which buffer to render to, +etc. + +We need a way to bundle this contextual information and have entity +implement its rendering in terms of that context. + +## The Pattern +**Base class** defines a **contextual operation** as an abstract +method that takes a **context object**. Context object maintains state +needed to perform operations, and exposes **basic operations** that +use it. Derived **concrete class** implements the contextual operation +by calling methods on the context object. + +## When to Use It +* When the context and operations needed aren't already available + either globally (see Service) or from the base class itself (see + Subclass Sandbox). + +* When the context needed to perform an operation is not owned by the + class defining the operation. (In our example, Entity doesn't own + the render state.) + +## Keep in Mind + +## Design Decisions + +## Sample Code + +## See Also + +* Sandbox works similarly, but used protected methods in base class + instead of passed in object. \ No newline at end of file diff --git a/draft/outline/double-buffer.markdown b/draft/outline/double-buffer.markdown new file mode 100644 index 0000000..3600254 --- /dev/null +++ b/draft/outline/double-buffer.markdown @@ -0,0 +1,297 @@ +^title Double Buffer +^theme Sequencing +^outline + +## Intent +*Enable a series of sequential operations to appear to occur +simultaneously.* + +## Motivation + +* computers are essentially serial: they do one thing at a time. + if your game has mountains in the distance, trees closer, and a + couple of monsters roaming on screen, those are all drawn one at + a time each frame. if the user saw them drawn like that, illusion + of a coherent scene would be broken. instead, the whole series of + rendering calls needs to appear to happen instantly. + +### How A Graphics System Works + +* monitor refreshes every 60th of a second regardless of what + program is doing. it needs to know what to display +* answer is a framebuffer: a chunk of ram that represents the values + of each pixel +* if you write to the framebuffer while the screen is refreshing + you'll get tearing +* even if you time it right, unless you can draw + *everything* in one v-blank, the user would see your program draw + stuff little bits at a time + +### What's Behind the Curtain + +* analagous to a play. to change the stage setting, they close the + curtain. this way the audience doesn't see the change. they just + see Scene 1, Curtain, Scene 2. +* better than watching stagehands drag props around, but the + audience still has to stare at a curtain. +* imagine this: behind the audience, we set up a projector and a + digital camera. we point the camera at the stage. right at the + end of scene 1, the camera takes a picture. the curtain (white) + drops and right then the projector turns on and projects the + picture onto the curtain. +* to the audience, they never saw the curtain at all. scene 1 just + froze. the entire time the stagehands are setting up scene 2, + the audience is looking at the *previous* scene, frozen in time. + +### Back To The Graphics + +* that's how graphics work. there are *two* framebuffers: one is + what the audience sees, and one is where the stagehands are + building the next scene. when the stagehands are done, the buffers + are *swapped* and the new one becomes the current scene, and the + old one is now offscreen and can be drawn on. +* do this once every thirtieth of a second and the user sees smooth + animation. +* this is the basic idea behind double buffering: you have two work + areas, the current one and the next one. the next one is worked + on serially. meanwhile, all external systems only see the + unchanging current one. once the new one is done, a quick switch + is done and now it's current. this frees up the old current one + to become the new work area. + +## The Pattern + +A **buffered class** encapsulates a **buffer**: a piece of state that +can be modified. This buffer is edited incrementally, but we want all +outside code to see the edit as a single atomic change. To do this, +the class keeps *two* instances of the buffer, a **next buffer** and a +**current buffer**. + +When information is read *from* a buffer, it is always from the +*current* buffer. When information is *written* to a buffer, it occurs +on the *next* buffer. When the changes are complete, a **swap** +operation swaps the next and current buffers instantly so that the new +buffer is now publicly visible. The old current buffer is now +available to be reused as the new next buffer. + +## When to Use It + +* need to modify state incrementally + +* other stuff will be accessing that state during that time + +* want the modification to appear instantaneous or simultaneous + +## Keep in Mind + +* doubles the amount of memory needed for state + +## Sample Code + +### A Simple Graphics System + +* a bit hard to show a typical sample because it relies on the + video hardware calling into our code while we're doing something. + this will be a bit hand-wavey. + +* we'll be showing a very simple graphics system that lets you draw + rectangles onto a canvas. first, canvas: + + canvas class + clear() + drawRect(x, y, width, height) + getFrameBuffer(); + +* drawRect() does what you think. getFrameBuffer(); gets the raw + memory for the canvas. this is what the video device needs to + render. + +* wrapping this is a scene class. it's job is to draw all of the + stuff we expect to see on screen. + + scene class + mCanvas + draw() + bunch of canvas drawRect calls + getCanvas() + +* so, every frame, the game tells the scene to draw. it clears the + canvas and then draws a bunch of rectangles. + +* if we just went with this, we'd have tearing problems or at the + very least would see the scene in the middle of being drawn. so, + double-buffering: + + scene class + mCurrentCanvas*, mNextCanvas* + draw() + draws to mNextCanvas + calls swap at end + swap() + swaps pointers + getCanvas() + gets mCurrentCanvas + +* now scene has two canvases. the publicly visible one is never + drawn directly onto. this solves our tearing problems. + + there's another private canvas that is the working area. draw + draws onto that. then, at the very end, it calls swap(). swap + just switches the two pointers, making the newly drawn canvas now + the public one. also, the old public one is now hidden, so we can + reuse it as our new work area. + +### Not Just For Graphics + +* what you saw was the canonical double buffer use case, but far + from the only one. to give you an idea of how the pattern is + generally useful, let's cover another example that couldn't be + more removed from graphics: ai. + +#### Artificial Unintelligence + +* example has three slapstick comedian actors. each frame, they get + update call. during that, can send messages and respond to + messages. update should be simultaneous: order that actors update + should not matter. + + actor class + update() = 0 + mWorld + receiveMessage(int message) + mMessage = message + getMessage + +* all of the actors in the game are held in a stage object, so we + can keep track of them + + stage class + actors[] + update() + update each actor + send(int actor, int message) + actors[actor].message = message + +* our example actors interact + + class Moe + update() + if !done + world->send(LARRY, SLAP) + done = true + + class Larry + update() + if message == SLAP + world->send(CURLY, EYE_POKE) + + class Curly + update() + if message == EYE_POKE + print "ow!" + +#### Roll the Film + +* let's say our actor collection on the stage is ordered like this: + Moe, Larry, Curly + +* when we update, we update Moe first. he sends the slap to Larry. + next we update Larry. since he got the slap, he sends the eye + poke to curly. finally we update curly, who receives the poke and + cries out. this all happens in one frame. + +* now lets re-order the collection to Curly, Larry, Moe. the first + update, Curly does nothing since he hasn't gotten a message. next + Larry does nothing. finally Moe slaps Larry. the *next* frame, + curly still does nothing, but Larry gets the slap Moe sent on the + last frame and pokes Curly. on the third frame, Curly finally + gets the poke and shouts. + +* so we get visibly different behavior, just by reordering. goes + against requirement that update is simultaneous. + +#### Buffered Messages + +* solve by double-buffering messages. actor: + + actor class + update() = 0 + mWorld + receiveMessage(int message) + mNextMessage = message + getMessage + return mCurrentMessage + swap() + mCurrent = mNext + mNext = NONE + +* now when an actor receives message, during update, it will go into + the next buffer, which won't be looked at until the next frame. + +* after the stage updates() all actors, it swaps the buffers on them + all. + +* *show that actors now behave same regardless of collection order + +## Design Alternatives + +### How are the Buffers Swapped? + +* swap pointers + + * quickest way to swap + + * may not work for systems that expect "current" buffer to be in + a certain place in memory + + * existing data on next buffer will be from two frames ago, not + last frame. + +* copy buffer data + + * can take time. need to ensure nothing is looking at buffers + while this is happening + + * means data on next buffer is never more than one frame old + +### What is the Granulatiry of the Buffer? + +* in render example, the entire buffer is a single object that there + are two instances of. in actor example, each actor has its own + tiny double buffer. + +* monolithic + + * simplifies swapping. don't need to interate through collection + doing incremental swap. usually faster. + +* distributed + + * keeps "double-buffer" implementation detail encapsulated + within the object that needs it + + * swapping can be slower. must tell each object to swap. + + may be able to optimize by having a global "swap index": + + class actor + messages[2] + static sCurrentIndex + static sNextIndex + + static swap + swap current and next indices + + getMessage() return messages[current] + receiveMessage() messages[next] = value + +## See Also + +* unix pipeline model allows processes to send information to each + other reliably even though they run in parallel. to do so, + information on pipeline is queued. sender adds to queue, receiver + reads from it. the queue acts as buffer to decouple processes + from each other. double buffer pattern is basically a simplified + version of this where the queue is always two objects deep. + +* SwapBuffers() in opengl, EndDraw() in XNA diff --git a/draft/outline/game-loop.markdown b/draft/outline/game-loop.markdown new file mode 100644 index 0000000..ff103a3 --- /dev/null +++ b/draft/outline/game-loop.markdown @@ -0,0 +1,124 @@ +^title Game Loop +^section Sequencing Patterns +^outline + +## Intent + +*Simulate the progression of time and run smoothly independent of user input.* + +## Motivation + +- old programs used to be batch: ran and quit +- then came interactive programs. these ran forever, which meant loop +- if you write gui app, the os has event loop + - you receive ui event, handle it, and return to event loop + - js is a great example + - you don't see loop, but it's there + - when app isn't handle user input, it isn't doing anything, just sitting + there + - this can work for simple games (minesweeper, solitaire) +- game loop + - games are living breathing worlds. need to keep simulating independent of + user input. don't wait. + - even turn based games often have animation that needs to keep going even + when user isn't doing anything + - solution is game does loop itself. polls for input instead of blocking + +### Two Kinds of Time + +- two kinds of time: user's wall clock time. game's simulation time (ticks) +- exchange rate between them: how much *work* can the cpu handle in a given + amount of real time. old games could hard code exchange rate. +- modern hardware is too flexible: run on range of devices, multitasking, + battery life, etc. +- need to dynamically calculate exchange rate + +- out of scope + - networking and coordinating time + - threading: sim on one, render on another, etc. + +## The Pattern + +A **game loop** runs continuously until the game is exited. Each turn of the +loop, it **reads user input**, then **updates the game state**, then **displays +the game**. It will also keep track of the passage of time so that it can +control the **rate of gameplay**. + +## When to Use It + +One of the few things common to almost every game. You will almost always use +it. + +Counterexample is non-realtime games that rely on OS GUI. If you write a +Minesweeper clone, may not to code a game loop. Still there, though: it's the +OS's event loop. + +may not write it yourself. if using game engine, engine will usually provide it. this is one of the main differences between a "library" and an "engine" or "framework". with engine, it owns the main loop and calls your code. lib is other way. game loop is drive shaft of engine. + +will likely even use this for turn-based games since animation, vfx, etc. is +realtime. a turn-based game is just a realtime game where the enemy doesn't +move until after the player does. + +## Keep in Mind + +- need to coordinate with os' event loop +- consider different hardware capabilities game will run on. the more known + they are, the simpler your game loop +- running as fast as possible on mobile can harm battery life + +## Sample Code + +### Ye Olde Game Loope + + main() + while (notDeadYet) + readInput() + updateGame() + render() + +fine in atari days where you knew exact hardware you were running on and could +own entire cpu. + +bad these days. + +### Fixing the Framerate + + + +- if machine is fast enough game runs at reliable speed +- game still runs at right speed if machine is faster +- battery-life friendly +- game slows down if machine is too slow + +### Decouple Update and Render Rate + + + +- uses max power +- updates with fixed time step +- can get into spiral of death +- doesn't play more smoothly on better hardware + +### Interpolated Rendering + + + +## Design Decisions + +### How does it adapt to fast hardware? + +### How does it adapt to slow hardware? + +### Does power consumption or CPU usage matter? + +- if the loop has sleep(), battery-friendly. if not, optimizes game experience + +## See Also + +http://gafferongames.com/game-physics/fix-your-timestep/ +http://www.koonsolo.com/news/dewitters-gameloop/ +http://en.wikipedia.org/wiki/Game_programming +http://active.tutsplus.com/tutorials/games/understanding-the-game-loop-basix/ +http://www.mvps.org/directx/articles/writing_the_game_loop.htm +http://entropyinteractive.com/2011/02/game-engine-design-the-game-loop/ +http://www.nuclex.org/articles/3-basics/5-how-a-game-loop-works diff --git a/draft/outline/hello-world.markdown b/draft/outline/hello-world.markdown new file mode 100644 index 0000000..01c0694 --- /dev/null +++ b/draft/outline/hello-world.markdown @@ -0,0 +1,76 @@ +^title Hello World + +Here's a general summary of the problem or problems that this pattern +can solve. It's a fairly short introductory paragraph to the pattern. +Note that it lacks a header. It ends with a single *emphasized +summary of the pattern*. + +## The Pattern +This is the main expository section of the pattern. It explains what +the pattern is in terms of an example application. Although it +references an example, it does not use code to explain the pattern. +This is still the pattern in fairly general terms. + +> This is a sidebar section. These can be used anywhere in the book +> to provide additional, but not necessary, information about the +> following passage. +> +> It has two paragraphs. + +This section will likely be a page or two long, and will often contain +diagrams or other helpful illustrations. + +## Implementation +This longer section walks through the important skeleton of an +implementation of the example pattern described in the previous +section. It will not necessarily show *all* of the code required to +implement the pattern, just enough to get the idea across. + +Because of the step-by-step nature of this section, and the frequent +code samples, this will likely be the longer part of the chapter. + +Just for format testing, here's an ordered list: + +1. This is the first item. It has a pretty long body of text so that + we can ensure that text wrapping is working correctly. + + * And this is a nested list. It has a pretty long body of text so + that we can ensure that text wrapping is working correctly. + + 1. Three levels of nesting should be plenty. Make it nice and + long so we can ensure word wrap wraps at the right place. + + * Second nested item. + +2. This is the second item. + +3. This is the third item. + +Here is one code sample: + +^code 1 + +Just to test code inclusion and ensure it's working right, here's +another: + +^code 2 + +## Implications + +* In bullet form, lists the implications of using this pattern. + +* Includes pros. + +* Also includes cons. + +## Applications +This short section describes common uses of the pattern. It may be +omitted if not useful. + +## Related Patterns +Patterns are often used in tandem or in place of other patterns. This +section, if relevant, describes other patterns that often touch on +this one. + +**How are refinements handled? Do they get all of the sections the +full pattern gets?** diff --git a/draft/outline/introduction.markdown b/draft/outline/introduction.markdown new file mode 100644 index 0000000..0dbe3c7 --- /dev/null +++ b/draft/outline/introduction.markdown @@ -0,0 +1,113 @@ +^title Introduction + +* my personal history + * taught myself to program + * always wanted to write games, like every one else + * could find resources for graphics, sound, etc. but struggled on + personal projects. once they reached a certain size, they became + unweildy messes and i gave up. + * in 2001, got a job at ea. + * couldn't wait to see how real game teams organized their + codebases. + * not what i was expecting: there were some architectural gems, + but a lot of their codebases looked like my unweildy messes. + they justthrew more bodies at the problem and soldiered on. + * disappointing, but inspiring too: i know it can be done better. + some parts of codebases i saw were really brilliant. why doesn't + everyone know how to put things together like that? + * this book tries to help that: get the smart things i saw some + people doing and collect them together so that everyone else can + learn from it. +* what's in store + * not like other game books: no graphics algorithms, etc. instead, + provides so you can build an architecture to house those things. + if a game is a house, graphics and sound are light fixtures and + appliances. this book will teach you how to put in the wiring + and plumbing. + * this won't teach you how to write an entire game engine. but if + you've ever worked on a game engine and wondered why it was so + hard to add features to it, or change, this book aims to help. + +## How it Relates to Design Patterns +* this is essentially an extension of gof's book. while you don't + necessarily need both, if i were you and i could only buy one of + these books, i'd buy theirs. almost every pattern in design + patterns can find an effective use inside a game engine. if you're + doing significant architecture on a game, you should be familiar + with them. +* once you have those down, this book picks up from there with more + patterns that are rarely encountered outside of games. +* this also covers some patterns from the gof book, but with a + clearer emphasis on how they can be used in games. if you know + patterns, but have trouble knowing when to use them, this may + help. +* some patterns here are smaller in scale. design patterns tries + to restrict itself to just architectural patterns. this book + has those, but also smaller scale implementation patterns. + +## How This Book is Structured and Formatted + +* overview and intro struff + * if this were a recipe book (and it is), this would be how to + measure ingredients, cut meat, etc. + * how the book is intended to be used + * how it relates to other material on patterns + * how to apply it to your everyday programming +* collection of patterns + * these are the recipes + * patterns are grouped into several themes. each theme describes + two opposing forces that cause problems which must be reconciled + to make a game. for each theme, several design patterns are + provided. + * each pattern is structured like + * introduction: describes the problem the pattern solves + * the pattern: using an example usage, describes each step and + part of the pattern. no code samples yet. + * implementation: demonstrates and explains a sample + implementation of the previously described example. not all + code is included, just the meat required to get the point + across. + * implications: the pros, cons, requirements and side-effects + of using this pattern are explaining in a bullet list + * applications: if useful, a short section explaining common + places where this pattern is used. for example: the command + pattern is often used for undo, etc. + * related patterns: if the pattern is often used in + conjunction with others, or otherwise references them, + mention them here. + * refinements: some patterns may be expanded upon or modified. + if applicable, describe common refinements here. **still + need to figure out exactly how this will be integrated into + the overall pattern chapter.** +* formatting + * code is in a `monospace font` + * material that may be interesting but isn't necessary to know may + show up in sidebars + * patterns have components. each component is in **bold face** + when it is first defined + * patterns described in this book are Capitalized (1) with the + page number of the pattern's chapter after the name. + +## Example Code + +* none of the patterns in this are particularly bound to a single + programming language. whatever language you're using can probably + handle them. so the choice of example language is kind of arbitrary. +* sample code is in c++ + * most game programmers know it, or at least know a language like + Java or C# with similar syntax + * consistent with GoF + * some patterns deal specifically with memory or static type + system limitations, and c++ lets you work in those domains. + * complete working programs are not presented here. you're a smart + coder and can fill in the blanks with obvious stuff. keeps us + focused on the heart of the pattern. + * the coding style is one that works best for a book, not for + production code. may not be the best style guide. + +## Evolving + +* patterns by nature are constantly growing and changing to reflect + the community that uses them. your involvement is an important + part of the process. if you have suggestions, corrections, ideas, + or problems with the what's in here, please get in touch. \ No newline at end of file diff --git a/draft/outline/service.markdown b/draft/outline/service.markdown new file mode 100644 index 0000000..5110b4f --- /dev/null +++ b/draft/outline/service.markdown @@ -0,0 +1,95 @@ +^title Service +^theme Communicating +^outline + +## Intent +*Provide a global point of access to a service, without coupling users +of the service to the concrete class that implements it.* + +## Motivation +Many systems in a game need to cause sounds to play. Want to provide +global access to ability to play sound. + +Usually use a Singleton for this, but that still couples to concrete class, and also brings in an unneeded single instance limitation. + +Better is to separate out the class providing *access* to a service +from the class *implementing* it. This way, game is only coupled to +abstract service class, and not any concrete audio classes. + +## The Pattern +**Service Provider** class provides a globally-accessible reference to an instance of a **Service**, an interface for a set of operations. At +runtime, an instance of a **Service Implementation** is registered +with the provider. + +## When to Use It +Use it when many things will want access to a single content-independent module: audio, networking, visual effects. + +Very useful when there may be multiple implementations of the service. + +## Keep in Mind +* It's global, anything globally accessible can still cause unwanted + interdependencies. Don't overuse. + +* Unlike Singleton, Service isn't automatically instantiated. Make + sure you register a service before it's used. + +* Service doesn't have to be *global*. Could also limit it to a + base class. For example, audio service is registered with the + base Actor class, which then exposes it to all subclasses. + +* Have to go through v-table for calls. May not be efficient enough + for services used very frequently. + +## Design Decisions + +* What is the scope of the service (who can use it)? Global? + Namespace? Class? Narrowed minimizes coupling, but can cause you + to have multiple classes store reference to same service. Means + you have to be careful that the service is registered everywhere + it needs to be. + +* Who registers the service? One place in code will be coupled to + both the service provider, and the concrete implementation. + +* What happens if service before implementation is registered? + + Can handle this with a **Null Service**: an implementation of the + service that does nothing. Return instance of this if you try + to use Service without registering implementation. Allows you to + safely turn off features, such as removing audio during + development. + +* What is the API for the service itself? This is a public interface + that will be used and implemented frequently. Take care to design + it well. + +## Sample Code + +### The Service +*Show an example AudioService API, an abstract class with a couple of +PlaySound() methods.* + +*Show an example implementation, AudioImpl (bodies of functions will +not actually be implemented).* + +### A Simple Provider +*Show static class AudioProvider. Has methods to get and set +AudioService pointer.* + +### Service Unavailable +*Show NullAudioImpl null service class. Show how AudioService returns +a pointer to that if no other service is registered.* + +### Logging Decorator +Logging is useful during development, but don't want the performance +hit in the shipping game. Often use #ifdefs for this, but we can also +use Service and Decorator to a cleaner effect. + +*Show AudioLogger class that decorates AudioService by logging and +then forwarding call to wrapped instance.* + +*Show how code registering service can conditionally wrap in logging +when desired.* + +## See Also +*Compare to Singleton.* \ No newline at end of file diff --git a/draft/outline/spatial-partition.markdown b/draft/outline/spatial-partition.markdown new file mode 100644 index 0000000..1431727 --- /dev/null +++ b/draft/outline/spatial-partition.markdown @@ -0,0 +1,110 @@ +^title Spatial Partition +^section Optimizing Patterns + +## Intent + +*Quickly find objects near a certain point or area by storing them in a data +structure that organizes them by location.* + +## Motivation + +- games inhabit virtual world. that world often mimics the real world. +- one facet of that is things have a position and interact in physical space. +- in other words, you can be near stuff, and stuff can collide. +- obvious example is physics and collision, but other stuff too. + - for online games, maybe chat messages are only heard by other players near + you + - only render things near the player (though visibility culling is a bit + different) + - audio system may take into account which objects are nearby +- all of this boils down to, given a location in the game world, what's nearby +- we'll pick collision as example +- for collision, need to see which pairs of objects are touching. +- comparing each pair in whole world is n^2! +- as number of objects increases, gets worse and worse +- think about hash table + - hash table lets you find object with key in constant time instead of n for + searching the whole collection + - what if we could make a hash table where the key is its position? + - this is basic idea of spatial partition: organize objects in data structure + based on where they are + - literal hash table with positions as keys wouldn't work, though. often need + to find objects *near* a position. hash tables, by design, don't handle + approximate keys well. they intentionally spread objects out so that a tiny + key change is a big location change. + +## The Pattern + +- create data structure where objects are organized based on location in game + world +- allow efficient queries to find objects at or near a location + +## When to Use It + +- when objects have position +- doing frequent queries to find nearby objects +- there are a lot of objects + +## Keep in Mind + +- goal is generally to reduce a n^2 operation to n or a n to 1. if number of + objects is actually small, may not be worth it. + +- if objects can move, they have to be reorganized in the data structure. + there is some overhead to this (especially with hierarchical spatial + partitions) so make sure benefits outweight organization + +- data structure has some memory overhead. as usual, trading memory for perf. + +## Sample Code + +- lots of spatial partition data structures. area of active research. +- since we just care about pattern, we'll do the simplest one: a fixed grid. +- rts. lots and lots of units running around battlefield. need to see which + units are near enough to engage in melee. +- naive solution + - for each unit + - for each other unit + - skip redundant comparisons + - bounding box test + - see if distance < minimum +- naive solution is n^2. +- want to have *lots* of units and game is slowing down. +- introducing grid + - overlay 2d grid overworld. each cell has a fixed size. + - each cell stores list of units + - units whose position falls within that cell are in that cell's list +- finding collisions + - for each unit + - find its cell + - for each other unit in cell + - now same as before + - still seems to be n^2: two nested loops + - but since there are many cells, each cell will have fewer units +- neighboring cells + - if near cell boundary, may be touching units whose centerpoint is in + neighboring cell. need to take those into account too. + +## Design Decisions + +- which specific spatial partition to use. lots of options: grid, bsp, etc. +- does it adapt automatically to the set of objects or is it fixed like a grid? + - fixed is simpler + - may be faster too + - adaptable works better for large levels, or with user-authored content +- some are optimized for objects that don't move +- are objects *only* stored in spatial partition or is there a separate direct + list of them too? + - former uses less memory. can be slow if you need to visit all objects and + most of the spatial partition is empty. +- is it hierarchical or not? + - hierarchical works better when objects are non-evenly distributed ("clumpy") + - flat is simpler + +## See Also + +* http://en.wikipedia.org/wiki/Space_partitioning +* http://en.wikipedia.org/wiki/Grid_(spatial_index) +* http://en.wikipedia.org/wiki/Kd-tree +* http://en.wikipedia.org/wiki/Binary_space_partitioning +* http://en.wikipedia.org/wiki/Quad_tree diff --git a/draft/outline/subclass-sandbox.markdown b/draft/outline/subclass-sandbox.markdown new file mode 100644 index 0000000..449b262 --- /dev/null +++ b/draft/outline/subclass-sandbox.markdown @@ -0,0 +1,204 @@ +^title Subclass Sandbox +^section Behaving Patterns +^outline + +## Intent + +*Create varied behavior while minimizing coupling by defining subclasses that only use operations provided by their shared base class.* + +## Motivation + +Making superhero action game. Have lots of different superheroes with +different powers. Each will be subclass of Superhero. Each does +different things and touches different systems: audio, vfx, ai, etc. + +Basic implementation would have subclasses coupled to all sorts +of other game systems by calling them directly. Problems: + +* May be lots of redundancy in superpower code. Lots of powers do similar + things. Want high level API to express them. + +* If many people on team are implementing superheros, they won't know + which parts of the engine should be called into, and which + shouldn't. End up doing all sorts of bad things, touching systems + not meant for public use. + +* If you need to change a system, will have to touch every superhero + using it. + +* Lot of times, base class wants to enforce properties of its + subclasses. Say superhero wants to ensure all audio calls get + queued. Since subclasses talk to audio directly, can't get in way. + +* Calling into other system is often too low-level and may not have + a friendly API. + +What we want is to say a superhero can perform these and only these +operations. Each superhero subclass gets a sandbox where it can work, +and a set of toys it can play with. Sandbox is an abstract method in +the base Superhero class. Toys are protected methods in base class. +Making them protected and non-virtual tells the user "these methods +are for you to use". + +Derived superheroes are now coupled only to base class, and +programmers implementing them don't have access to call random +functions all over game engine. + +Operations provided by Superhero can be designed specifically to be +easy and simple to use for subclasses, and can hide some of the +machinery needed to talk to raw system. + +There is often one base class with a lot of subclasses (shallow but wide hierarchy). Patterns like this put more effort into base class to make subclasses easier to write. Since there's lots of subclasses, this is a net win. A little time and love put into Superhero benefits many classes. So idea is that we design Superhero to have protected methods that define the nicest API we can for creating a new kind of superhero. Superclass exposes a DSL to subclass. + +## The Pattern + +**Base class** defines an abstract **sandbox method**. Also provides +several protected **allowed operations**. Marking them protected +makes it clear to a user that they are for use by derived classes. +Derived **sandboxed subclass** implements sandbox method by calling +inherited allowed operations. + +## When to Use It + +* Very common pattern. Probably already using it. + +* When all classes defining behavior share a base class. + +* When the base class is able to provide all of the operations. + +## Keep in Mind + +* Can make base class overly heavy. It ends up providing + *everything* a derived class would need. Can lead to brittle base + class problem. + + If that happens, consider moving some operations into Context + Objects or into objects which are accessible from base class. + +* Keeps derived classes coupled only to base class. This is a good + decoupling strategy. A small number of core base classes are + coupled to each other. A larger set of derived "leaf" classes are + coupled only to their parents. Since most work is in leaf classes, + minimizes dependencies in code most people are working in. Hub and spoke + architecture. + +## Design Decisions + +* Which/how many operations to provide? + + * Can go far down the path so that subclass *only* talks to superclass. + * Can dial back so that subclass talks to base for some stuff (usually + stateful stuff) but allow coupling to certain things. + * The more superclass handles, the less coupled subclasses are, but the + messier superclass becomes. Takes coupling out of subclasses, but just + dumps it into superclass. Superclass can become huge and unweildy. + + * Rules of thumb: + + *If you're adding something to superclass that will only + be used by one or two subclasses, not getting a lot of bang for buck. + Just let them call it directly. + + * If method doesn't modify any state, then coupling directly to it is + less risky (since a subclass calling it can't break anything). + + * If the method you put in superclass is just a straight forwarding method + and isn't easier to use than calling it directly, then it doesn't add + as much value. + +* Provide operations directly in base class, or provide objects + which in turn provide methods? + + * Providing method directly is simpler and best for methods that + are logically related to class's primary role. + + *Show derived entity class calling SetPosition method to move + itself.* + + * Wrapping methods in the base class reduces derived class's + coupling. Aside: Law of Demeter is really "rule of thumb of Demeter". + + * Exposing objects reduces the number of methods base class has + to implement. + + *Show code where derived class calls a protected method to get + a AudioService object from base class, then calls play sound + method on it.* This way base class doesn't have to wrap every + sound function. + + * Exposing object lets you change service without having to also + edit wrapper methods. + +* How does base Superhero class get state it needs? + + Base class often needs state that it wants to hide from + subclasses. For example, provides a nice simple playSound() + method. But implementation of that needs access to audio system, + which we don't want derived classes to know about. Since base ctor + is called from derived one, passing it in through ctor can expose + stuff we don't want. + + * Pass through ctor: + + * Simple + * Ensures heroes are always fully initialized + * Exposes base class to implementation details + * If Superhero later needs more state, have to touch every + derived ctor. + + * Two-stage initialization: + + Superhero* createSuperhero(SuperheroType type) { + Superhero* hero; + switch (type) { + case AWESOME_GUY: hero = new AwesomeGuy(); break; + case FLAMING_MAN: hero = new FlamingMan(); break; + case THE_TOASTER: hero = new TheToaster(); break; + } + + hero->init(soundSystem, ...); + + return hero; + } + + * Keeps derived classes decoupled from state + * Have to make sure you don't forget to init(). Usually best to + wrap both stages in single factory method. + + * Static state. + + Hero has private *static* fields. Provides non-static protected accessors. Hero *class* must be initialized once at game startup. + + * Not bad like singleton since only subclasses have access to it. + * Doesn't increase size of Hero instance. + * Requires all Heroes to share exact same state. + + * Service locator or lazy init: + + Hero gets what it needs on its own later when it needs it. + + * Can't forget to init like two-stage. + * Keeps derived classes decoupled from state. + * Can be slow or unpredictable. + * Hero has to make sure to check that it's inited state + before it uses it. + +## Sample Code + +*Show Superhero class with abstract OnTakeTurn() method and protected +methods for Move(x, y), PlaySound(SoundID), etc.* + +*Show derived AwesomeGuy class that overrides OnTakeTurn() and calls +some of the protected methods in Superhero.* + +## See Also + +* The base class may implement the operations it provides by + forwarding to a Service that it owns. + + *Show sample base class with a pointer to a Service and a + protected operation method that forwards to that.* + + This keeps derived classes coupled only to base, and not service. + +* This pattern is basically Context Parameter, where the parameter is `this`. \ No newline at end of file diff --git a/draft/outline/type-object.markdown b/draft/outline/type-object.markdown new file mode 100644 index 0000000..4ffa879 --- /dev/null +++ b/draft/outline/type-object.markdown @@ -0,0 +1,427 @@ +^title Type Object +^section Behaving +^outline + +## Intent +*Allow new "classes" to be defined easily by creating a single class each instance of which describes a different type of object.* + +## Motivation +* we're tasked with implementing the monsters for a game. monsters + have a bunch of properties: health, damage, graphics, etc. + +* there are many different breeds: trolls, dragons, gnomes, etc. and + the breed determines many of the stats: every troll has the same + attacks and art, etc. + +### The Typical OOP Answer + +* we're using an oop language, so we start defining classes. first + a base Monster class + + class Monster + virtual int GetDamage() = 0; + virtual string GetAttackText() = 0; + + then we define subclasses: + + class Troll + GetDamage return 4 + + class Dragon + GetDamage return 20 + +* all is well and good and before long we have dozens of monster + subclasses. + +* now starts to bog down. every time we want to add a new monster, + we have to create a new class and recompile. when a designer wants + to change a stat, they have to ask a coder to do it. slow! + +* what we want is for designers to be able create and modify monster + classes on their own, but they don't know c++ + +### A Class for a Class + +* the key observation is that our subclasses don't really do much + expect provide different values. if that's all we need, we don't + need to use c++'s type system. + +* instead, we'll define our own type that represents a breed of + monster. it has fields for the damage, attacks, and other + breed-specific properties. + +* each monster instance will have a reference to its breed. every + monster of the same breed points to the same breed instance. + +* when the monster needs breed-specific data, it pulls it from its + breed ref. + +* the nice thing now is that everything about a monster breed is + stored in member vars. if we give breed a ctor that reads those + from a file, we now have completely data-driven monster types. + designers can create a new data file on their own and get a new + breed in game. + +## The Pattern + +Define a **type object** class and an **object** class. Each +*instance* of the type object class represents a different logical +type. Instances of the object class store a reference to the type +object that describes their type. + +Instance-specific data is stored in the object instance, and data or +behavior that should shared across all instances of the same +conceptual type is stored in the type object. Objects referencing the +same object will function as if they were the same type. This lets us +share data and behavior across a set of similar objects much like +subclassing lets us do, but without having a fixed set of hard-coded +subclasses. + +## When to Use It + +* you need to share data/behavior between instances. if each monster + was totally unique, this wouldn't buy us anything. + +* you want greater flexibility that subclassing gives you: either + you don't know the set of subclasses you'll need, or you want to + be able to define them in data + +## Keep in Mind + +* each type object instance must be instantiated and kept in memory + unlike raw classes which are automatically put into static + memory by the compiler. + +* tends to be more limited than subclassing. with subclasses, you + can override a method and do anything you want. with type objects, + you're usually limited to just storing different values. can + make them more expressive by using function pointers. if you're + defining metaclass instances in data, one option is to use + bytecode to make them more expressive. + +## Sample Code + +### A Basic Type Object + +start simple. for our example, monsters have health and a string they +use when attacking. the current health is instance specific (it goes +down when you punch it in the face), but the starting health is +determined by the breed. the breed also determines the attack string. + +we'll start with the breed. + + class breed + breed(starting, attack) + + int getstartinghealth { return mstarting; } + const char* attack { return mattack; } + +pretty simple. it's basically just a container for some data: starting +health and the attack. now let's show a basic monster + + class monster + monster (int health, breed) + attack() + cout << mbreed->attack + breed* mBreed + int mhealth + +when we create a monster now, we give it a breed now instead of +subclassing. attack function uses breed to get string. + +as it stands, not using starting health from breed. the assumption is +that we'll use it when we construct the monster. we can codify that +assumption and make things a little more solid by making type object +even more like a class: we'll give it responsibility for constructing +monsters + + class breed + monster* new() + return new monster(mstartinghealth, this) + +the new method in breed now makes a monster and properly sets the +breed and starting health. now, to outside code, the breed really is +the entry point for monsters: you create a new one by asking the +breed to instantiate it. + +we can make this more explicit by making another slight change: + + class monster + private ctor + friend class breed + +now, *only* breed can make monsters. our type objects now work like +classes in languages like ruby and smalltalk where you create new +instances by asking the class to. + +### Making it Data Driven + +now we can define monsters that share attributes, but how is this +better than subclassing? the important part is that the breed class +is just a data container now. this means it's easy for us to define +a breed in a data file like xml: + + + 100 + The orc punches you in the face! + + +and then we can right some simple code to parse the file and construct +breeds: + + loadbreed(xml) + int health = xml.getnode("health").asint; + char* attack = xml.getnode("attack"); + return new breed(health, attack) + +(this isn't a book on xml, so you'll have to image we have a nice xml +lib here.) + +now not only do we get the benefits of subclassing, but we can now +define new breeds without having to touch a line of code or recompile. +so easy a designer could do it! + +### Sharing Data Through Inheritance + +what we have so far is perfectly serviceable especially for our simple +breed example. imagine we have hundreds of different breeds, each with +dozens of different props. if a designer wants to make all of the +thirty different elf breeds a little stronger, it's going to suck to +have to edit that in thirty places. + +in oop languages, we can solve problems like this using inheritance. +let's see if we can add that to our type object system. we just do +single inheritance. each breed gets a parent breed: + + class breed + breed(startinghealth, attack, breed* parent) + breed * mparent + +to have the parent breed be useful, a child needs to be able to +indicate what properties its overriding and what it's leaving alone. +a real-world system may do something more complex, but for our little +example, we'll say that breed overrides by having non-zero health +or non-null attack. if zero or null, just inherit value: + + class breed + gethealth + if health != 0 return health + return parent->gethealth + getattack + if attack != null return attack + return parent->attack + +now, if we were defining breeds in xml, we could do something like: + + (aside: studies show ! in attack makes the game 10x more + exciting!) + + + 25 + The elf hits you! + + + The archer fires an arrow! + + + The wizards casts a spell on you! + + +if we construct the breeds appropriately for that data, and default +any missing prop to zero or null, then the elf wizard and archer will now inherit their health from the parent elf breed. if our designer +laters want to change the health of all elves, they only need to +touch that. + +### More Powerful Overriding + +what we have now is powerful enough to cover most use cases you'll +ever encounter. but, for kicks, lets talk about a more powerful +system. + +with what we have so far, overriding means just replacing the value +with the one the parent provides. how about being able to combine the +parent and child values in different ways? let's ignore the attack +stuff for now and just focus on health. consider: + + class breed + breed(breed* parent, float multParenthealth, int addparenthealth) + + gethealth + int health = 0 + if parent != null) health = parent->gethealth + + health *= multparenthealth + health += addparenthealth + + return health + +with this, our child breeds can now override the parent health by +applying a multiplier and/or an additive bonus to it. with this, we +could give a base health for elf, and then make a warrior elf whose +health is the base * 1.5 + 10. if we want to totally replace the +parent health, just use a zero multiplier. + +you probably won't use this, but if you find yourself building a very +comprehensive content definition system, it's something to think about. + +### Objective-C and Real Type Objects ### + +the hoops we've jumped through so far let us create an object that +represents a class in c++ because in c++, classes themselves aren't +objects. let's look at a language where they are: obj-c (others too: +smalltalk, etc.) + +we can implement same pattern in objective c, but make our breed +instances actual obj-c classes. + +**bob: obj-c magic!** + +## Design Alternatives + +### encapsulate the type or expose it? + +encapsulating means that to outside code, objects are essentially +typeless. the fact that groups of them happen to share certain +properties is coincidental, and the internal type objects are just an +implementation detail to make that work well. + +if its exposed, the type objects become part of the contract of the +system itself: all objects have a type. + +* encapsulate + + * outside code is abstracted away from use of type object + pattern + + * object class can selectively override to provide + instance-specific behavior + + * have to write forwarding methods for everything the type + supports in object class + +* expose + + * outside code can interact with class directly without + being associated with a specific object. for example, + the type object can have a "ctor" that outside code can + call. + + * type object pattern is now part of public api of class. + +* mix - expose type object through a restricted interface that + lets you do some stuff on the type directly but requires you + to go through an object for others. + + * lets you control which things are object-overridable and + which are tied only to the type object itself + + * more complex + +### how are objects constructed? + +each object is now a pair: an object and the type object it uses. how +is this pair built? + +* construct object and pass in type + + * lets outside code control where object is allocated. + + * simpler + +* call "ctor" function in type object, which returns object + + * type object controls allocation. could be good if you want + it to use its own object pools. could be bad if outside code + wants to use *its* own object pools. + + * gives type object a chance to do other validation and initing, + for example, monster has a current health. the starting value + for that comes from the breed. if the breed constructs + monster, it can initialize that properly. + + * i like this option. it feels more oop-like: you're basically + calling a ctor. + +### can the type change? + +we usually assume that once an object is created, its type object is +locked down, but it could change. + +for example, when a monster is killed, we could change its breed to a +special "corpse" breed so that we can see the body on the ground. + +* type object doesn't change + + * simpler. lines up with oop assumptions. + + * easier to debug. + +* type object can change + + * less swapping out of objects. for example, a shape-shifter + can simply swap its type object. without that, we'd have to + create a new object with the new type, copy any + object-specific values over and discard the old one. + + * need to be careful that type object's assumptions aren't + invalidated. object and its type are usually tightly coupled. + for example, a breed may assume the monster's health is + always within some breed-dependent range. if we swap the type + but leave the existing health, that invariant may no longer + be true. + + when swapping types, we may need to perform a validation step + to make sure the object's per-instance state makes sense with + the new type too. + +### what kind of inheritance is supported? + +this is a bit open-ended. when designing a type object system, you're +basically designing a type system for a programming language: the +field is wide open to support inheritance, multiple inheritance, +mixins or categories, or any other feature you can come up with. + +can't cover all cases, but will provide some notes for a few: + +* flat types, no inheritance etc. + + * simplest, which is often best. + + * for data-driven types, easiest for non-technical users to + author and understand. + + * fastest, don't have to walk inheritance chain. + + * can lead to a lot of duplication across types. i've yet to see + a system where designers *didn't* want inheritance: when + you've got 50 different kinds of elves, having to tune their + health by changing a number in 50 places *sucks*. + +* single inheritance. + + * pretty simple to implement and understand. good compromise + between simplicity and allowing data reuse. + + there's a reason a lot of programming languages stop here: it + seems to be a sweet spot. + +* multiple inheritance + + * more powerful and flexible. lets you do things like Zombie + Dragon which is both a Dragon and a Zombie. + + * complex to understand. which properties are controlled by + which base classes? users need to understand how the system + traverses the inheritance graph. + +## See Also + +* some languages support this intrinsicly: Obj-C, Smalltalk + +* another option is to use prototypes and not have "classes" at all + +* this is similar in some ways to the state pattern: the type object + defines *some* state for the object, which happens to be shared + across multiple instances. if you change the type object an + object is using, it will appear to change its class, much like + changing the state in the state pattern does. diff --git a/draft/outline/update-method.markdown b/draft/outline/update-method.markdown new file mode 100644 index 0000000..5471367 --- /dev/null +++ b/draft/outline/update-method.markdown @@ -0,0 +1,316 @@ +^title Update Method +^section Sequencing Patterns + +## Motivation + +- hero trying to enter crypt. have undead guard patrolling entrace, back and forth. not very smart, but very loyal. + +- do straight imperative code: + + while true + for x = 0 to 100 + set pos + render + + for x = 100 to 0 + set pos + render + +- code is monopolizing cpu. no other entities on level get to do anything + no input handling. no game loop. + +- want to add statue enemy that fires at player every few frames + + frame = 0 + while true + for x = 0 to 100 + entity1.set pos + if frame++ % FIRE == 0 then entity2.fire + render + + for x = 100 to 0 + entity1.set pos + if frame++ % FIRE == 0 then entity2.fire + render + +- nasty, all code for entities is mixed together. imagine we want to add + another patrolling entity that moves at a different speed. +- want to be able to mix and match entities on levels +- adding entities shouldn't affect others +- means want each entities behavior to be self-contained + +- solution is to split out behavior for each entity with entity +- instead of code that does behavior over multiple frames without returning, + only simulates one frame +- everywhere it used to render frame, now returns/yields +- can then handle multiple entities by interleaving these calls +- call it once each from on every entity +- now entities run concurrently and independently + +## Pattern + +??? + +## When to Use It + +- most real-time game uses this, called directly from game loop +- turn-based games may use it too, but decoupled from game loop: only call when + entity has turn +- if game entities/pieces are particularly simple and don't do much (chess, + checkers), may not be needed +- use when: + + - have number of game entities or systems + - entities have own independent behavior + - need to move over time + +## Keep in Mind + +- splitting up behavior into one frame at a time makes code more complex + + - almost always need to do this anyway to play nice with game loop + - very hard to control framerate with straight line imperative code + +- have to remember where you left off each frame + + - lot of temporary state has to be moved into fields on the entity so that + it persists across calls + + - state pattern can help + + - vm pattern too + +- behavior is more concurrent but still turn-based + + - each entity gets slice of time, but order that they are processed in + still matters since later entities will see updated state of earlier + ones + + - is mostly a good thing. to do real simultaneous sim, would need double + buffer. + + - lots of memory and complexity. + - bad behavior: two entities see square is empty and both try to move + into it in parallel. what do? + - sequential is almost always better. + - TODO: how do physics systems handle this? research + +- need to be careful about modification of entity list + + - when using update method pattern, lot of game behavior exists in the middle + of iterating over list of entities (or components) + - often game behavior means changing that list + - monster drops treasure: need to add treasure + - can usually just add it to end of list + - keep in mind this means entity will get a chance to act in the same + frame that it was spawned in, before it renders + - may be good + - may be bad because player doesn't have chance to see it + - if update loop stores number of entities in list before it starts + looping, can stop before new entities + - then they don't get processed until next turn + - you kill enemy: needs to be removed + - if you immediately remove and enemy was next in list, loop may skip + over an entity + - if using something like a linked list, can end up in bad memory + - be careful + - may want to defer list removals until after update loop + - add to list of "dead" entities and then remove them later + +## Sample Code + +- have a basic entity class + + class Entity + int x, y + +- give abstract update method + + update() + +- game engine has collection of entities in game + + code... + +- main game loop updates every entity once per frame + + gameloop(): + for entity in entities: entity.update() + +- typically does this before physics and rendering + +- then define subclasses for different kinds of entities with different behavior +- using subclassing here is heresy for many. used to be absolutely most common + way, but components are more popular now. in practice, probably would use + component. not here for simplicity. + +- start with patroller + + class SkeletonGuard : Entity + update() + ... + +- earlier, had local variable for position and just had for loop to walk from one side to other +- now that behavior has to be split across multiple update calls, variable has to be hoisted out of local and into field +- for loop is gone. (game loop basically drives that for us.) +- have to add other variable to remember which direction going. +- in earlier code, that was implicit based where in function we were. in first loop, going right, in second left. cpu's instruction pointer kept track of this. +- now, since we return after each frame, we have to remember that explicitly so we can resume where we left off. +- often end up with something state-machine like for behavior. see state chapter + +- now do statue + + class Statue : Entity + update() + ... + +- pretty simple + +- what did we learn? + + - compared to imperative code for single entity, splitting into single-frame slices is a lot of work. + - have to hoist local variables into fields + - have to add some state to remember where in behavior we were + - but compared to imperative code for *multiple* entities, much simpler. where before we had statue code mixed into patrol loops, that's all elegantly separated out. + - code for statue is actually simpler than it was before. + - since almost all games have multiple entities on screen, this is almost always a win. + - now we can add and remove entities at whim. can even have *data* determine which entities level has. just drop entity on and it will do what it's supposed to. + +- one other entity in game: hero! +- can just make him an entity too +- only difference is that update() looks at user input +- literally only difference +- automatically ensures player and enemies behavior is correctly synchronized + +## Design Decisions + +### What class does `update()` method live on? + +**Main entity class** + +- simplest +- no other classes +- means subclassing for each kind of entity +- industry moving away from this + +**Component** + +- if using component pattern already, components will have update method +- main class then just automatically calls update on all components +- if game loop updates entities which then update components, can be pointless + pointer chasing. see structure of arrays +- probably most common pattern today + +**State** + +- if using fsm for entity, may have state object. +- can often put update there +- may let you reuse pieces of behavior across different entities if states are + meaningful for more entities +- changing states gives you simple way of changing behavior +- avoids having to subclass each entity + +### What order are entities updated in? + +- to user, entities seem to behave simultaneously, but really are taking turns +- just that turn is 1/60 second +- since it is + +**Arbitrary** + +- simplest answer is just whatever order they happen to be in the list +- new entities get added to the end, stuff gets removed +- for real-time games, this usually works fine +- "turn" is only a single frame long, so order isn't really user visible +- as long as every entity only gets one update per turn, and the order is stable, + it's fair +- as long as you don't shuffle the list, no entity will get to go twice before + any other does + +**Prioritized** + +- if doing turn-based game, may actually having a timing system where some entities + move "faster" (i.e. more frequently) than others +- explain amaranth-style energy system? +- priority queue? + +### What do you iterate over? + +- each frame, have to call update() methods of every object that needs updating +- when entities are added/removed, need to update this collection +- how do we do that efficiently? + +**entities** + +- simplest: just collection of all entities in game +- probably have this collection already +- if have lots of entities that don't need updating (dormant, inactive, etc.) + can spend a lot of time walking over entities for no reason +- if using component model, can spend lot of time visiting entities just to + look up component + +**components** + +- if using component model, update is usually handled by component, not entity + itself +- in that case, walking entity just serves to get you to component +- can optimize by having list of just those components +- then walk that directly +- have to make sure to keep this updated when entities added/removed +- don't want zombie component +- see structure of arrays pattern + +**updatable entities** + +### How is collection stored? + +**dynamic array** + +- appending to end is fast, but removing is slow +- can use something like object pool to make removal faster: leave empty holes + and fill later +- need to be careful because that affects update order + +**linked list** + +- deletion is fast +- insertion too, but usually just adding to end anyway +- can do a lot of pointer chasing and cache invalidation + +TODO: does that matter? array is array of pointers anyway (since vtable) so +pointer chasing either way? + +## See Also + + +- random notes + + - very old games used to orchestrate all enemies. that's why you had lots more + patterns of enemies instead of them behaving independently. + - talk about looping through all entities here. game loop doesn't cover that. + - game loop just calls update(), so isn't coupled to details of how each + entity/system does its thing + - compare to using threads for each entity which means os scheduler would + control stuff. bad! + - have a bunch of things in the game that are all moving "simultaneously" + - not literally at same time: even real-time games are a little more formal + and everything actually takes its turn + - if really tried to be at same time, would need to make sure entities only + accessed *previous* state. would need to double buffer. makes things much + more complex. instead, entities are sequential. + - don't want high level code telling each entity "ok you do this, you do that" + - instead, want each entity to own its own behavior. that way changing one + entity in level doesn't affect others. + - hard thing about update method is that have to get imperative code and slice + it into tiny chunks. have to remember where you were so can pick up after + where you left off. state pattern can help. vm pattern can too in different + way. + - if using component pattern, entity update will usually just delegate to + components. + - important that update method is fast. if one entity takes a long time, slows + whole game. + - don't necessarily need strict 1-entity to 1 update method system. if game + play works on groups of entities (formations, etc.), could have update for + entire group + + diff --git a/note/bio.txt b/note/bio.txt new file mode 100644 index 0000000..22d3b91 --- /dev/null +++ b/note/bio.txt @@ -0,0 +1,3 @@ +Robert Nystrom has programmed professionally for twenty years, about half of which is in games. During his eight years at Electronic Arts, he worked on behemoths like Madden and smaller titles like Henry Hatsworth in the Puzzling Adventure. He's shipped games on the PC, GameCube, PS2, XBox, X360, and DS, but is most proud of the tools and shared libraries he created for others to build on. He loves seeing usable, beautiful code magnify the creative ability of others. + +Robert lives with his wife and two daughters in Seattle where you are most likely to find him cooking for his friends and plying them with good beer. diff --git a/note/description.txt b/note/description.txt new file mode 100644 index 0000000..6a9a0cf --- /dev/null +++ b/note/description.txt @@ -0,0 +1,3 @@ +The biggest challenge facing many game programmers is completing their game. Most game projects fizzle out, overwhelmed by the complexity of their own code. Game Programming Patterns tackles that exact problem. Based on years of experience in shipped AAA titles, this book collects proven patterns to untangle and optimize your game, organized as independent recipes so you can pick just the patterns you need. + +You will learn how to write a robust game loop, how to organize your entities using components, and take advantage of the CPUs cache to improve your performance. You'll dive deep into how scripting engines encode behavior, how quadtrees and other spatial partitions optimize your engine, and how other classic design patterns can be used in games. diff --git a/note/dimensions.txt b/note/dimensions.txt new file mode 100644 index 0000000..3e8a65f --- /dev/null +++ b/note/dimensions.txt @@ -0,0 +1,52 @@ +Here's some dimensions of popular programming books on Amazon: + +minecraft for dummies 5.4 x 8.6 x 0.3 inches +touch develop 6 x 9 x 0.6 inches +don't make me think 7 x 9.1 x 0.5 inches +learning python 7.1 x 9.3 x 2.8 inches +beginning c++ 7.2 x 9 x 1.1 inches +node.js in action 7.4 x 9.2 x 0.8 inches +unity 4 by example 7.5 x 9.2 x 1.2 inches +html & css 7.9 x 9.6 x 1 inches +invent your own games 8 x 10 x 0.9 inches + +Print on demand printers: + +lulu.com +createspace.com +lightningsource.com +blurb.com (seems to target mostly photo books) +bookbaby.com + +Based on those and looking at prices on Lulu, I think the best choice is: + +Product Line: Standard +Binding: Perfect Bound Paperback +Product Size: Crown Quarto (7.44 x 9.68 inch) +Interior Color: Black & White +Paper Quality: 60# +Cover Finish: Gloss +Cost per 280 page book: $9.80 + +On CreateSpace: + +Interior Type: Black and White +Trim Size: 7.5" x 9.25" +Number of Pages: 280 +Quantity: 1 +Per Book: $4.21 each + + +Margins: + +InDesign Classroom in a Book: + Bottom: ~0.375" + Outside: ~0.375" + Top: ~0.5" + Inside: ~0.875." + +SICP: + Top: ~0.5" + Outside: ~0.75" + Bottom: ~0.75" + Inside: ~1.0" diff --git a/note/fonts.txt b/note/fonts.txt new file mode 100644 index 0000000..5813daf --- /dev/null +++ b/note/fonts.txt @@ -0,0 +1,88 @@ +Body +---- + +Maybe: + +Albertina +Andron +Bembo +Berthold Baskerville Book +Cala (Hoftype) + http://www.myfonts.com/fonts/hoftype/cala/ + *Very* readable. Nice minimal thickness changes should balance well with code + font. Nice ligatures. Love the italics. +Caslon +Century Schoolbook +Dante + http://www.myfonts.com/fonts/mti/dante-mt/ + Looks nice. Vertical "o" looks nice and modern. Italics are a bit too old + style. +Dolly +Electra +Elena + http://processtypefoundry.com/fonts/elena/ + Nice modern serif. A bit brushstroke-y. +Fenland +Girando Pro +Harriet Text Regular (Okay Type) + Sweet Jesus, it's heavenly. May get played out in a few years. Super + fashionable right now. +Lyon +Mercury +Sabon Next + http://www.linotype.com/240/sabon.html + Very readable, very lucid. I think it will pair well. A bit too boring and + too similar to Times New Roman. +Sina Nova + http://www.fontsquirrel.com/fonts/sina-nova + Very nice. Similar to Cala. +Sirba + http://www.type-together.com/Sirba + Very beautiful. Modern, a bit informal. Would match the prose well. +Tundra + https://www.fontfont.com/fonts/tundra + Beautiful, clean, lucid. Very strong contender. Maybe a bit too square and + narrow. + +No: + +Janson - Classy, but a bit thin at points. +Jenson - Beautiful font but too old style for my writing. +Minion - Too 90's, overdone. +Scala - Too angular. +Augustin + http://www.myfonts.com/fonts/ludwiguebele/augustin/ + Pretty. Italics are nice. Tail on "y" and "j" are distinctive but not in a + way that would fit well with the book. Might clash with the code font. +Comenia + OK. Has a matching sans, but no light weights. Regular weight is a bit heavy. +Documenta - Too old style. +Expo - Meh. Too... artsy? Feels like it would be good for a history text, but + not my book. +Iowan OS + http://www.myfonts.com/fonts/bitstream/iowan-old-style/ + Nice. A bit plain. Diamond dots over i's. +Miller Text Regular - Similar to Harriet. Very pretty, but a bit too dense. +Milo Serif - Not a fan. +Novel + http://www.myfonts.com/fonts/atlas-font-foundry/novel-std/ + Clean and pretty. The italics are meh. +Quadraat - "B" is WTF. Italics too angular. +Stuart - Neat, but not the right character for the book. +Storm Baskerville 10 - Too Victorian. +Verdigris - Very beautiful, similar to Jenson and Sabon. Too old style. +Yoga - Chunky. + +http://www.typophile.com/node/100581 +http://www.myfonts.com/users/pjy2ao1bxc/albums/591331/ + +Sans +---- + +News Gothic +JAF Bernini Sans + +No: + +Franklin Gothic - Too heavy. +Folio - Too wide. diff --git a/note/log.txt b/note/log.txt new file mode 100644 index 0000000..d17518f --- /dev/null +++ b/note/log.txt @@ -0,0 +1,947 @@ +2014-10-08 - finish layout, proofread, re-upload to createspace for review +2014-10-07 - layout through data locality +2014-10-06 - layout through event queue +2014-10-05 - layout through subclass sandbox +2014-10-04 - layout through behavioral patterns +2014-10-03 - layout through singleton (!) +2014-10-02 - decimal inches page layout +2014-10-01 - work on new page layout +2014-09-30 - work on new page layout +2014-09-29 - cover and other epub front matter +2014-09-28 - email +2014-09-27 - go through bugs and pull requests +2014-09-26 - finish applying proofreading, tweak other web stuff +2014-09-25 - apply proofreading to web of six chapters, top nav +2014-09-24 - more kdp and bowker stuff, description, cover +2014-09-23 - fill in stuff on createspace and kdp +2014-09-22 - finish proofreading +2014-09-21 - apply proofreading through type object +2014-09-20 - apply proofreading through game loop +2014-09-19 - apply proofreading through observer +2014-09-18 - proofread last three chapters! +2014-09-17 - proofread data locality +2014-09-16 - proofread through service locator +2014-09-15 - proofread through component +2014-09-14 - proofread through bytecode +2014-09-13 - proofread through double buffer +2014-09-12 - update enter actions in print version +2014-09-11 - redo enter actions in state +2014-09-10 - proofread through half of state +2014-09-09 - proofread through flyweight +2014-09-08 - cover! +2014-09-07 - print! +2014-09-06 - finish front matter +2014-09-05 - acknowledgements +2014-09-04 - more index revision, grep styles +2014-09-03 - more index revision, grep styles +2014-09-02 - index last three chapters, and revise index! +2014-09-01 - index data locality +2014-08-31 - index component, event queue, and service locator +2014-08-30 - index subclass sandbox and type object +2014-08-29 - index update method and bytecode +2014-08-28 - index double buffer and game loop +2014-08-27 - clean up indexes so far +2014-08-26 - index singleton and state +2014-08-25 - index flyweight, observer, prototype +2014-08-24 - index intro, architecture, command +2014-08-23 - start working on index +2014-08-22 - fix all the cross refs +2014-08-21 - work on cross references +2014-08-20 - clean up blank pages, fix bugs, pay lauren +2014-08-19 - layout spatial partition (last chapter!) +2014-08-18 - fix #253, merge spatial partition, layout object pool +2014-08-17 - fix #252 +2014-08-16 - merge object pool changes +2014-08-15 - email, set up group for translators +2014-08-14 - finish dirty flag, renumber front matter +2014-08-13 - start laying out dirty flag +2014-08-12 - rescan illustrations +2014-08-11 - merge dirty flag +2014-08-10 - figure out which illustrations need rescanning +2014-08-09 - email (1 :( ) +2014-08-08 - layout data locality +2014-08-07 - merge data locality changes +2014-08-06 - layout service locator +2014-08-05 - merge service locator +2014-08-04 - layout event queue +2014-08-03 - merge event queue +2014-08-02 - layout component +2014-08-01 - merge component changes +2014-07-28 - email +2014-07-27 - layout type object +2014-07-26 - merge type object changes +2014-07-25 - print table of contents +2014-07-24 - email +2014-07-23 - ebook table of contents +2014-07-22 - layout subclass sandbox +2014-07-21 - fix #234 and merge subclass sandbox changes +2014-07-20 - email +2014-07-19 - start sketching cover illustration +2014-07-18 - more cover comps +2014-07-17 - finish bytecode +2014-07-16 - layout behavioral patterns, start on bytecode +2014-07-15 - merge bytecode changes +2014-07-14 - layout update method +2014-07-13 - merge update method changes +2014-07-08 - email +2014-07-07 - layout game loop +2014-07-06 - merge in changes to game loop +2014-07-05 - work on toc for epub +2014-07-04 - work on styling for epub +2014-07-03 - more work on format script for epub, alt text for images +2014-07-02 - more work on epub +2014-07-01 - start working on epub version +2014-06-30 - finish double buffer +2014-06-29 - sequencing patterns, start working on double buffer +2014-06-28 - merge changes from lauren +2014-06-27 - layout state +2014-06-26 - 6 emails +2014-06-25 - lay out singleton +2014-06-24 - lay out prototype +2014-06-23 - lay out observer +2014-06-22 - finish command, lay out flyweight +2014-06-21 - start laying out command +2014-06-20 - layout architecture, start building book +2014-06-19 - akkurat -> source, layout intro +2014-06-18 - merge pull requests, including lauren's changes +2014-06-17 - figure captions and start working on front matter +2014-06-16 - turn links to chapter references +2014-06-15 - title and section pages +2014-06-14 - running footer, new column proportion +2014-06-13 - work on illustration workflow +2014-06-12 - 13.5pt baseline grid +2014-06-11 - more hand layout and style tweaking +2014-06-10 - start hand laying out object pool, more style work +2014-06-09 - massage xml a bunch to make indesign happy +2014-06-08 - more work on xml and masters +2014-06-07 - fight with xml +2014-06-06 - work on masters and styling xml +2014-06-05 - add xml support to format script, start figuring out how to import +2014-06-04 - fix four bugs, send money to lauren +2014-06-03 - pick body font, start laying out masters +2014-06-02 - lots of design work +2014-06-01 - start playing with design +2014-05-30 - start learning indesign and toying with design +2014-05-29 - fix bugs in one chapter +2014-05-28 - fix bugs in two chapters, four emails +2014-05-27 - fix four bugs +2014-05-26 - fix #207, reformat event queue +2014-05-26 - four bugs +2014-05-25 - revise last 2182 words of component +2014-05-24 - revise 2449 words component +2014-05-23 - revise type object +2014-05-22 - revise subclass sandbox and behavioral patterns. reformat bytecode +2014-05-21 - revise update method +2014-05-20 - revise game loop +2014-05-19 - BROKE CHAIN +2014-05-18 - 4 emails, 6 bugs +2014-05-17 - finish revising double buffer +2014-05-16 - revise 2526 words of double buffer +2014-05-15 - revise state +2014-05-14 - revise singleton +2014-05-13 - revise prototype +2014-05-12 - revise last 2700 words of observer +2014-05-11 - revise 2213 words observer, handle long code lines +2014-05-10 - revise flyweight +2014-05-09 - catch up on email, 1 bug +2014-05-08 - 8 bugs, photoshop illustration +2014-05-07 - revise design patterns revisited and command +2014-05-06 - redo illustration +2014-05-05 - 1 bug, revise architecture chapter +2014-05-04 - 7 bugs +2014-05-03 - 6 bugs +2014-05-02 - 5 bugs, 3 emails, make design sections consistent +2014-05-01 - 3 bugs, work on style guide for chapter structure, work on making keep in mind sections more consistent +2014-04-30 - 6 emails, 3 bugs, revise introduction +2014-04-29 - 8 emails, 4 bugs +2014-04-28 - 12 emails few bug fixes +2014-04-27 - 20 emails +2014-04-26 - 4 pull requests, 10 bugs +2014-04-25 - 4 pull requests, 21 bugs +2014-04-24 - redo navigation (#146, #148) +2014-04-23 - reddit, hn, twitter, email, write blog post, research ebook +2014-04-22 - photoshop illustrations, send to mailing list, COMPLETE +2014-04-21 - third draft of architecture +2014-04-20 - #130, #134, #135, #136, #139 +2014-04-19 - #123, #125, #128, #129, merge style guide +2014-04-18 - fix #137, email +2014-04-17 - smart quotes and design changes +2014-04-16 - address #116 #119 #120 #122 #124 #126 #131 #133 +2014-04-15 - email lb +2014-04-14 - 481 words, finish second draft +2014-04-13 - 967 words, second draft, email possible proofreader +2014-04-12 - 1194 words, second draft +2014-04-11 - 978 words, second draft +2014-04-10 - 883 words, finish first draft arch +2014-04-09 - 1348 words, arch +2014-04-08 - 265 words, first draft arch +2014-04-07 - 710 words, first draft arch +2014-04-06 - 715 words, first draft arch +2014-04-05 - 852 words, finish outline arch +2014-04-04 - 215 words, outline arch +2014-04-03 - 313 words notes for arch chapter +2014-04-02 - start notes for arch chapter +2014-04-01 - finish mobile index +2014-03-31 - work on mobile version of index +2014-03-30 - redo front page +2014-03-29 - finish new design +2014-03-28 - work on new design +2014-03-27 - email email email +2014-03-26 - fix #103 #109 #110 #111 #115, merge #108 #114 #118, close #106 +2014-03-25 - post new chapter, mailing list, twitter, proggit, g+ +2014-03-24 - 2136 words, finish bytecode +2014-03-23 - 2448 words (delete ~100), third draft bytecode +2014-03-22 - 808 words, third draft bytecode +2014-03-21 - 1521 words, third draft bytecode +2014-03-20 - photoshop bytecode illustrations +2014-03-19 - 9 illustrations for bytecode +2014-03-18 - 1401 words, finish second draft bytecode +2014-03-17 - 733 words (delete ~70), second draft bytecode +2014-03-16 - 1433 words (delete 33), second draft bytecode +2014-03-15 - 651 words (delete 172), second draft bytecode +2014-03-14 - 1151 words (delete ~150), second draft bytecode +2014-03-13 - 736 words (delete ~50), second draft bytecode +2014-03-12 - 520 words (delete ~100), second draft bytecode +2014-03-11 - 593 words, second draft bytecode +2014-03-10 - 843 words second draft bytecode +2014-03-09 - 286 words (delete 207), second draft +2014-03-08 - 1197 words, finish first draft bytecode +2014-03-07 - 758 words, work on design decisions +2014-03-06 - 748 words, work on design decisions +2014-03-05 - 708 words, finish sample code +2014-03-04 - 678 words +2014-03-03 - 583 words, first draft bytecode, work on sample code section +2014-03-02 - 1373 words +2014-03-01 - 639 words +2014-02-28 - 615 words, finish motivation +2014-02-27 - 785 words, catch up on email +2014-02-26 - 482 words, first draft bytecode +2014-02-25 - resolve 2 bugs, start writing sample code for bytecode +2014-02-24 - 598 words, finish outline bytecode +2014-02-23 - 1165 words, outline bytecode +2014-02-22 - 1069 words, outline bytecode +2014-02-21 - fix 3 (large) bugs +2014-02-20 - fix 3 bugs, move todos to github issues, more design work +2014-02-19 - nav bar on new design +2014-02-18 - fix 3 bugs, close 1 +2014-02-17 - fix 9 bugs, close 3 +2014-02-16 - 5 illustrations, decoupling section intro, mail, twitter, proggit +2014-02-15 - 548 words, finish third draft event queue +2014-02-14 - 1879 words (delete ~90), third draft event queue +2014-02-13 - 1420 words (delete 120), third draft event queue +2014-02-12 - 1108 words, third draft event queue +2014-02-11 - 994 words (delete 246), third draft event queue +2014-02-10 - photoshop illustrations for event queue +2014-02-09 - 6 illustrations for event queue +2014-02-08 - 479 words (delete 58), finish second draft event queue, email +2014-02-07 - 985 words (delete 178), second draft event queue +2014-02-06 - 1015 words (delete 110), second draft event queue +2014-02-05 - 1166 words (delete 271), second draft event queue +2014-02-04 - 952 words (delete 406), second draft event queue +2014-02-03 - 532 words (delete 99), second draft event queue +2014-02-02 - 1206 words (delete 282), second draft event queue +2014-02-01 - 811 words, finish first draft event queue, work on new design +2014-01-31 - 961 words, first draft event queue +2014-01-30 - 441 words, first draft event queue +2014-01-29 - 378 words, first draft event queue +2014-01-28 - 816 words, first draft event queue +2014-01-27 - 186 words, ~100 lines of sample code +2014-01-26 - 400 words, first draft event queue +2014-01-25 - 535 words, first draft event queue +2014-01-24 - 439 words, first draft event queue +2014-01-23 - 537 words, first draft event queue +2014-01-22 - 233 words, first draft event queue +2014-01-21 - 854 words, first draft event queue +2014-01-20 - 921 words, first draft event queue. fix #85, #88. +2014-01-19 - 689 words, finish outline event queue +2014-01-18 - 359 words outline +2014-01-17 - 708 words outline event queue +2014-01-16 - 305 words outline, start writing sample code +2014-01-15 - fix/close #69, #76, #78, #79, #80, #81, #82, #83 +2014-01-14 - 767 words outline even queue +2014-01-13 - 744 words outline event queue +2014-01-12 - research and 718 words of notes for message queue +2014-01-11 - research and apply for business license +2014-01-10 - finish ape +2014-01-09 - read half of ape +2014-01-08 - fix or close #44, #58, #59, #63, #64, #65, #67, #68, #73. Merge #60, #66. +2014-01-07 - publish chapter, tweet, r/gamedev, etc. +2014-01-06 - photoshop six illustrations, finish observer +2014-01-05 - revise section header and update prototype +2014-01-04 - 4 illustrations for observer, 1 for object pool +2014-01-03 - 1038 words, finish third draft observer +2014-01-02 - 1064 words, third draft observer +2014-01-01 - 1306 words, third draft observer +2013-12-31 - 293 words, third draft observer +2013-12-30 - 1228 words, third draft observer +2013-12-29 - reorganize chapters, write revised section intro +2013-12-28 - 2847 words, finish second draft observer +2013-12-27 - 1512 words, second draft observer +2013-12-26 - 703 words, second draft observer +2013-12-25 - 451 words, second draft observer +2013-12-24 - fix #56 and #57 +2013-12-23 - 1198 finish first draft observer +2013-12-22 - 740 words, first draft observer +2013-12-21 - 811 words, sample code and first draft observer +2013-12-20 - 566 words + sample code, first draft observer +2013-12-19 - 796 words, first draft observer +2013-12-18 - 493 words, first draft observer +2013-12-17 - 733 words, first draft observer +2013-12-16 - 524 words, first draft observer +2013-12-15 - 1022 words finish outline observer +2013-12-14 - 876 words outline observer +2013-12-13 - ~400 words outline observer + research +2013-12-12 - unit tests and "pooled binding" sample code for observer +2013-12-11 - work on sample code for observer +2013-12-10 - take pull request for #53, fix #51, #54, #55 +2013-12-09 - post chapter online, tweet, reddit, mailing list +2013-12-08 - 1928 words, finish data locality! optimization patterns intro +2013-12-07 - photoshop illustrations +2013-12-06 - 2709 words, third draft of data locality +2013-12-05 - 1817 words, third draft of data locality +2013-12-04 - 5 illustrations for data locality +2013-12-03 - 1982 words, finish second draft of data locality +2013-12-02 - 2267 words, second draft data locality +2013-12-01 - 616 words (cut 185) second draft data locality +2013-11-30 - 1821 words (cut 366) second draft data locality +2013-11-29 - address #48, #49, #46, #45 +2013-11-28 - fix #35, #34, #41, #42, #43 +2013-11-27 - 1287 words, finish first draft data locality! +2013-11-26 - 1189 words, sample code and start design decisions +2013-11-25 - 861 words, first draft of data locality +2013-11-24 - 763 words, first draft of data locality +2013-11-23 - 798 words, first draft of data locality +2013-11-22 - 1241 words, first draft of data locality +2013-11-21 - 877 words, first draft structure of arrays +2013-11-20 - 677 words, finish outline structure of arrays +2013-11-19 - 792 words outline sample code and start design decisions +2013-11-18 - 845 words, redoing sample code section +2013-11-17 - ugh more work trying to get a clean benchmark sample +2013-11-16 - work on sample code benchmark code +2013-11-15 - 92 lines of sample code, more outlining +2013-11-14 - 453 words, outline structure of arrays +2013-11-13 - 1059 words, outline structure of arrays +2013-11-12 - 394 words, outline structure of arrays +2013-11-11 - 672 words, outline structure of arrays +2013-11-10 - more research, benchmark with actors changing active state +2013-11-09 - component comparison benchmark +2013-11-08 - benchmarking, got array access one working +2013-11-07 - ugh more research +2013-11-06 - research and benchmarks, get cachegrind +2013-11-05 - research and benchmark for structure of arrays +2013-11-04 - post chapter to /r/gamedev +2013-11-03 - 1126 words, finish third draft prototype, publish +2013-11-02 - 1355 words, third draft prototype +2013-11-01 - 1110 words, third draft prototype +2013-10-31 - photoshop 6 images for prototype +2013-10-30 - 846 words, finish second draft prototype +2013-10-29 - 1709 words, second draft prototype +2013-10-28 - 953 words, second draft prototype +2013-10-27 - draw five illustrations for prototype +2013-10-26 - 739 words, finish first draft prototype +2013-10-25 - 702 words, first draft prototype +2013-10-24 - 992 words, first draft prototype +2013-10-23 - 1040 words, first draft prototype +2013-10-22 - 115 lines of code for prototype +2013-10-21 - 547 words, finish outline for prototype +2013-10-20 - 898 words outline for prototype +2013-10-19 - 89 loc and notes for observer +2013-10-18 - 387 words outline context parameter +2013-10-17 - fix four bugs +2013-10-16 - upload chapter, send to list, tweet, reddit, etc. +2013-10-15 - complete third draft for command +2013-10-14 - scan and photoshop command illustrations +2013-10-13 - draw 4 illustrations for command +2013-10-12 - 1564 words, finish second draft command +2013-10-11 - 1086 words, second draft command +2013-10-10 - 631 words, finish first draft command +2013-10-09 - 750 words first draft command +2013-10-08 - 669 words first draft command +2013-10-07 - 178 lines of code, 338 words first draft command +2013-10-06 - 876 words first draft command +2013-10-05 - 679 words finish outline command +2013-10-04 - 622 words outline command +2013-10-03 - ~300 words real outline for command +2013-10-02 - notes for command +2013-10-01 - reorganize table of contents, ditch message chapter +2013-09-30 - reddit comments, fix #27, #28, #31, #32, #33 +2013-09-29 - tweak flyweight, fix #26 and #30, upload, email, social +2013-09-28 - scan and process 10 illustrations +2013-09-27 - three illustrations for flyweight +2013-09-26 - revise all 2193 words, finish third draft of flyweight +2013-09-25 - revise all 2178 words, finish second draft flyweight +2013-09-24 - finish converting to tree then revise 965 words +2013-09-23 - add 298 words to flyweight, revise 1084 words to different example +2013-09-22 - finish first draft of flyweight +2013-09-21 - create real code for code samples for flyweight +2013-09-20 - 618 words, first draft flyweight +2013-09-19 - 449 words, first draft flyweight +2013-09-18 - 327 words, first draft flyweight +2013-09-17 - 863 words, first draft flyweight +2013-09-16 - revise sequencing patterns intro, draw bunch of illustrations +2013-09-15 - take notes for flyweight, write sequencing patterns section +2013-09-14 - write and profile different flyweight examples +2013-09-13 - fix nine bugs +2013-09-12 - publish dirty flag, post to twitter, reddit, mailing list +2013-09-11 - two more illustrations, finish 3rd draft dirty flag +2013-09-10 - 1633 words (keep in mind, sample code) 3rd draft dirty flag +2013-09-09 - process illustrations and draw two more +2013-09-08 - revise 1362 words, 3rd draft dirty flag +2013-09-07 - revise 367 words, finish 2nd draft dirty flag +2013-09-06 - revise 541 words (delete ~200) 2nd draft dirty flag +2013-09-05 - revise 800 words, write real sample code, 2nd draft dirty flag +2013-09-04 - revise 1285 words, trim another 100, 2nd draft dirty flag +2013-09-03 - revise 1357 words, trim about 200, 2nd draft dirty flag +2013-09-02 - 4 illustrations for dirty flag +2013-09-01 - 1019 revise design decisions, finish first draft of dirty flag +2013-08-31 - 916+ rewrite sample code and revise other stuff +2013-08-30 - 1454 rewrite first draft motivation in dirty flag +2013-08-29 - 1447 words revised in dirty flag +2013-08-28 - 723 words outline new motivation section for dirty flag +2013-08-27 - 372 words finish draft, research how it's used in other things +2013-08-26 - 1148 words, first draft of dirty flag +2013-08-25 - 841 words, first draft of dirty flag +2013-08-24 - 1104 words, first draft of dirty bits +2013-08-23 - 575 words, first draft of dirty bits +2013-08-22 - 600 words, finish outline dirty bits +2013-08-21 - 726 words outline dirty bits +2013-08-20 - 126 words outline dirty bits, photoshop 3 illustrations +2013-08-19 - fix 8 bugs, draw 3 illustrations +2013-08-18 - photoshop illustrations, then post update method chapter +2013-08-17 - 1500 words, finish 3rd draft of update method +2013-08-16 - 2027 words, 3rd draft of update method +2013-08-15 - drew a couple of illustrations for update method and others +2013-08-14 - 1511 words, finish 2nd draft of update method +2013-08-13 - 1090 words, 2nd draft of update method +2013-08-12 - 952 words, 2nd draft of update method +2013-08-11 - finish first draft of update method +2013-08-10 - ??? words on update method +2013-08-09 - 751 words on update method +2013-08-08 - fix #1, #2, #3. merge #6, 533 words on update method +2013-08-07 - 812 words, first draft update method +2013-08-06 - 895 words, first draft update method +2013-08-05 - 651 words outline update method +2013-08-04 - 1043 words outline update method +2013-08-03 - illustration for state, put online, mail list, reddit, etc. +2013-08-02 - 1837 words revised, third draft +2013-08-01 - 2118 words revised, third draft +2013-07-31 - embed sign-up form in call to action box and style it +2013-07-30 - call to action box, redo index, and make watch script watch css and template +2013-07-29 - create mailchimp account, add google analytics +2013-07-28 - 1041, finished second draft +2013-07-27 - 1434 words revised second draft +2013-07-26 - 895 words revised second draft +2013-07-25 - 713 words revised second draft +2013-07-24 - 183 lines, finish sample code for state +2013-07-23 - 409 lines of sample code for state +2013-07-22 - 217 words, end of first draft +2013-07-21 - 1138 words first draft of state +2013-07-20 - 771 words first draft of state +2013-07-19 - 932 words first draft of state +2013-07-18 - 772 words first draft of state +2013-07-17 - 860 words first draft of state +2013-07-16 - 535 words first draft of state +2013-07-15 - 736 words first draft of state +2013-07-14 - ~150 lines of code for state +2013-07-13 - put on github, ~200 lines of code for state +2013-07-12 - 684 words, finish outline for state +2013-07-11 - bunch of research on fsms and hierarchical fsms +2013-07-10 - 1268 words of tight outline for State +2013-07-09 - 298 words of outline for State +2013-07-08 - Pile of notes for State, make format script estimate +2013-07-07 - Finish third draft of Spatial Partition +2013-07-06 - revised 1524 words, finished second draft +2013-07-05 - revised 2577 words +2013-07-04 - 870 words, finish first draft of spatial partition +2013-07-03 - 636 words on design decisions +2013-07-02 - 622 words on design decisions +2013-07-01 - 329 words, really finished sample code section +2013-06-30 - 548 words, finished sample code section +2013-06-29 - [see 2013-06-25(2)] +2013-06-28 - 87 works on sample code, a bit more code +2013-06-27 - 581 works on motivation and sample code, some more code too +2013-06-26(2) - 373 words of sample code, ~50 lines of code, test script +2013-06-26 - More work on grid sample code +2013-06-25(2) - Unit tests and get grid working +2013-06-25 - Start writing grid code and tests for sample code, ~150 lines of code +2013-06-24 - 276 words on Sample Code for Spatial Partition, 100+ lines of code +2013-06-23 - 874 words on first draft of Spatial Partition +2013-06-22 - Outline Spatial Partition (743 words) +2013-06-21 - Finish updating asides and publish Game Loop +2013-06-20 - Work on CSS and asides +2013-06-19 - Figure out CSS/JS solution for asides on mobile +2013-06-18 - Finish third draft of Game Loop +2013-06-17 - 1,280 words revised in third draft of Game Loop +2013-06-16 - ~1,000 words revised in third draft of Game Loop +2013-06-15 - Finish second draft of Game Loop +2013-06-14 - Revise couple of paragraphs of Game Loop +2013-06-13 - Revise ~500 words of second draft of Game Loop +2013-06-12 - Revise ~900 words of second draft of Game Loop +2013-06-11 - Finish first draft of Game Loop +2013-06-10 - 489 words on first draft of Game Loop +2013-06-09 - 178 words on first draft of Game Loop +2013-06-08 - 889 words on first draft of Game Loop +2013-06-07 - 777 words on first draft of Game Loop + +2012/08/16 (:30) +- read a bunch of blog posts on game loops +- start outlining game loop +- revise index to link to twitter instead of blog + +-- big gap here where I wasn't taking notes + +04/13 (:45) ++ Install Office ++ Download and read Apress docs + +02/14 +- Revise Component (1:45) + text : 8 files, 3419 non-empty lines, 31327 words + note : 11 files, 1244 non-empty lines, 7039 words + code : 11 files, 1860 lines, 288 comments + +02/08 ++ Revise Component down to "How do components communicate..." (:45) + +01/28 ++ Revise Component down to Sample Code (:30) + +01/24 ++ Singleton / taking Andy's changes (:30) + +01/23 ++ Component / design decisions (1:50) 5,591 words + text : 8 files, 3379 non-empty lines, 31413 words + note : 11 files, 1225 non-empty lines, 6945 words + code : 11 files, 1852 lines, 288 comments + +01/22 ++ Component first draft / Sample code (2:10) + text : 8 files, 3263 non-empty lines, 30178 words + note : 11 files, 1220 non-empty lines, 6910 words + code : 11 files, 1784 lines, 282 comments + +01/21 ++ Component first draft / Keep in Mind (:45) + text : 8 files, 3164 non-empty lines, 28632 words + note : 11 files, 1213 non-empty lines, 6860 words + code : 10 files, 1931 lines, 293 comments + +01/17 ++ Component first draft / Motivation (1:00) + +01/17 ++ Component sample code (1:30) ++ Component outline (:20) + text : 8 files, 3120 non-empty lines, 28076 words + note : 11 files, 1206 non-empty lines, 6808 words + code : 10 files, 1931 lines, 293 comments + +01/16 +- Component sample code (1:30) + +01/12 +- outline Component (:20) + +01/10 ++ Type Object second revision (:20) + +01/09 +/ outline component / sample code (:40) ++ revise Type Object (1:40) (4973 words) + text : 7 files, 2774 non-empty lines, 25270 words + note : 11 files, 1194 non-empty lines, 6754 words + code : 9 files, 1431 lines, 233 comments + +01/07 ++ outline component / keep in mind (:20) + text : 7 files, 2639 non-empty lines, 24565 words + note : 11 files, 1193 non-empty lines, 6744 words + code : 9 files, 1431 lines, 233 comments + +01/04 ++ outline intro (:50) +/ outline subcomponent (1:10) ++ research game engines for entity class (:20) +- revise type object + +12/26 ++ take andy's fixes for object pool ++ switch section order for object pool +~ 1:00 hr + +12/22 +/ take andy's fixes for object pool +~ 2:00 + +12/21 +/ take andy's fixes for object pool ++ css support for definition lists +~ :30 hr + +12/21 ++ format script skips up-to-date chapters ++ draft type object / design alternatives +~ 1:45 hr + text : 6 files, 2460 non-empty lines, 22916 words + note : 11 files, 993 non-empty lines, 4920 words + code : 9 files, 1428 lines, 231 comments + +12/18 +/ draft type object / sample code +~ :30 hr + +12/14 +/ draft type object / sample code +~ :40 hr + +12/09 +/ draft type object / sample code +~ :45 hr + text : 6 files, 2420 non-empty lines, 22318 words + note : 11 files, 950 non-empty lines, 4645 words + code : 7 files, 1281 lines, 202 comments + +12/06 +/ draft type object / sample code +~ :30 hr + text : 6 files, 2377 non-empty lines, 21915 words + note : 11 files, 947 non-empty lines, 4619 words + code : 7 files, 1231 lines, 193 comments + +12/05 ++ draft type object / motivation ++ draft type object / when to use it ++ draft type object / keep in mind +~ 2:20 hr + text : 6 files, 2376 non-empty lines, 21859 words + note : 11 files, 941 non-empty lines, 4582 words + code : 0 files, 0 lines, 0 comments + +12/04 ++ outline type object +~ :45 hr + +12/03 ++ reorganize to support obj-c code +/ outline type object +~ 1:00 hr + +12/02 +/ outline type object +~ :30 hr + +12/01 +- research metaclass (type object) +~ 8:30 + +11/27 ++ revise double buffer +~ :45 hr + +11/22 +/ revise double buffer (done down to play analogy) +~ 1:00 hr + text : 5 files, 1978 non-empty lines, 18565 words + note : 10 files, 671 non-empty lines, 4154 words + code : 5 files, 1144 lines, 178 comments + +11/16 +/ double buffer / design alternatives ++ added 'back to top' to html +~ :45 hr + +11/15 ++ double buffer / sample code +~ 3:30 hr + text : 5 files, 1882 non-empty lines, 17626 words + note : 10 files, 661 non-empty lines, 4104 words + code : 5 files, 1114 lines, 175 comments + +11/13 ++ first draft double buffer / when to use it ++ first draft double buffer / keep in mind +/ double buffer code +~ :40 hr + text : 5 files, 1821 non-empty lines, 16818 words + note : 10 files, 655 non-empty lines, 4067 words + code : 5 files, 914 lines, 145 comments + +11/11 ++ first draft double buffer / motivation +~ 1:00 hr + text : 5 files, 1807 non-empty lines, 16584 words + note : 10 files, 647 non-empty lines, 4014 words + code : 4 files, 815 lines, 135 comments + +11/10 ++ outline double buffer +~ 1:00 hr + +11/03 +/ outline double buffer +~ :30 hr + +11/02 ++ service locator 2nd revision design alternatives +~ :30 hr + +10/29 ++ service locator second revision down to design alternatives + (down to 4559 words) +~ 1:15hr + +10/27 ++ revise service locator / motivation +~ 1:00 hr + text : 4 files, 1567 non-empty lines, 14872 words + note : 9 files, 618 non-empty lines, 3842 words + code : 4 files, 813 lines, 139 comments + +10/22 ++ revise service locator / design alternatives ++ revise service locator / see also +~ :45 hr + +10/21 +/ revise service locator / design alternatives (down to 4930) +~ :45 hr + text : 4 files, 1560 non-empty lines, 14615 words + note : 9 files, 612 non-empty lines, 3805 words + code : 4 files, 797 lines, 135 comments + +10/21 ++ revise service locator / keep in mind ++ revise service locator / sample code (down to 5105 words) +~ :45 hr + text : 4 files, 1575 non-empty lines, 14790 words + note : 9 files, 605 non-empty lines, 3755 words + code : 4 files, 797 lines, 135 comments + +10/20 +/ service locator revision (removed 31 words, down to 5341) +~ :45 hr + +10/18 ++ service locator / design decisions ++ service locator / sample code +~ 2:30 hr + text : 4 files, 1599 non-empty lines, 15026 words + note : 8 files, 632 non-empty lines, 3890 words + code : 4 files, 961 lines, 144 comments + +10/14 +- service locator / design decisions +~ 1:00 hr + text : 4 files, 1390 non-empty lines, 12990 words + note : 8 files, 622 non-empty lines, 3822 words + code : 4 files, 840 lines, 115 comments + +10/13 +- service locator / design decisions +~ :30 hr + text : 4 files, 1332 non-empty lines, 12407 words + note : 8 files, 616 non-empty lines, 3786 words + code : 4 files, 825 lines, 110 comments + +10/10 ++ service locator / when to use it ++ service locator / keep in mind +~ :45 hr + text : 4 files, 1264 non-empty lines, 11774 words + note : 8 files, 606 non-empty lines, 3712 words + code : 4 files, 785 lines, 102 comments + +10/08 +/ work on service locator +~ :30 hr + +09/22 ++ revise singleton ++ remove feedback stuff ++ smart quotes +~ 2:00 hr + text : 3 files, 1062 non-empty lines, 10025 words + note : 8 files, 531 non-empty lines, 3180 words + code : 4 files, 744 lines, 98 comments + +09/20 ++ revise singleton +~ 1:30 hr + text : 6 files, 1265 non-empty lines, 11787 words + note : 8 files, 520 non-empty lines, 3114 words + code : 4 files, 744 lines, 99 comments + +09/17 +/ revise singleton +~ 1:15 hr + +09/16 +/ revise singleton +~ :30 hr + text : 6 files, 1214 non-empty lines, 11381 words + note : 8 files, 506 non-empty lines, 3047 words + code : 4 files, 696 lines, 95 comments + +09/15 +/ revise singleton +~ :20 hr + +09/14 ++ draft singleton ++ revise introduction +~ :40 hr + +09/13 +/ draft singleton +~ :40 hr + text : 6 files, 1190 non-empty lines, 11195 words + note : 8 files, 490 non-empty lines, 2971 words + code : 4 files, 696 lines, 95 comments + +09/12 +/ draft singleton +~ 2:30 hr + text : 6 files, 1142 non-empty lines, 10717 words + note : 8 files, 485 non-empty lines, 2941 words + code : 4 files, 610 lines, 86 comments + +09/08 ++ service sample code +~ :45 hr + text : 5 files, 844 non-empty lines, 7990 words + note : 8 files, 476 non-empty lines, 2900 words + code : 3 files, 323 lines, 59 comments + +09/07 ++ css for links to other patterns ++ service motivation ++ service pattern and parts +~ 1:00 hr + +09/04 ++ Megan's revisions ++ revise fragments in outline ++ revise proposal ++ revise outline ++ put outline online ++ upload sample chapters and index ++ verify links in proposal work ++ illustrations ++ submit proposal!!! +~ 3:00 hr + +09/03 ++ revise outline to be full sentences ++ figure out other patterns to add ++ add example sections to each revisited pattern ++ fill in proposal ++ outline Area Effect ++ outline Update Method ++ outline Domain Component ++ outline Virtual Machine ++ outline Hit chapter ++ outline Metaclass chapter ++ outline Dead Pool chapter +~ 4:20 hr + 6:00 pm + +09/02 ++ outline html generator ++ outline css ++ outline 4 more patterns +~ 3:50 hr + +09/01 ++ research how Service compares to Singleton ++ outline Service ++ outline Sandbox Method +- outline Context Object +~ 3:30 hr + +08/31 ++ proposal + + tools + + writing sample ++ figure out average word count of oreilly book +~ 1:15 hr + +08/28 ++ feedback system ++ last modified on html ++ theme in title ++ upload html ++ tell people ++ script to upload html +~ 4:00 hr + text : 2 files, 647 non-empty lines, 6234 words + note : 7 files, 444 non-empty lines, 3061 words + code : 2 files, 252 lines, 57 comments + +08/27 ++ revise object pool ++ add feedback box to html +~ 3:30 hr + text : 2 files, 645 non-empty lines, 6230 words + note : 7 files, 431 non-empty lines, 3001 words + code : 2 files, 252 lines, 57 comments + + +08/26 ++ revise intro ++ improve nav bar in html +~ 2:30 hours + text : 2 files, 623 non-empty lines, 6043 words + note : 7 files, 424 non-empty lines, 2961 words + code : 2 files, 252 lines, 57 comments + +08/18 +~ 1:00 hour + +08/14 ++ draft object pool pattern +~ 3:15 hours + text : 2 files, 617 non-empty lines, 6032 words + note : 7 files, 414 non-empty lines, 2912 words + code : 2 files, 252 lines, 57 comments + +08/13 +/ draft object pool pattern + (in progress, still figuring out best structure) ++ fix list css +~ 3.5 hours + text : 2 files, 509 non-empty lines, 4932 words + note : 7 files, 413 non-empty lines, 2911 words + code : 2 files, 217 lines, 41 comments + +08/11 ++ research object pool ++ outline object pool chapter (all except implementation) ++ sample code for object pool chapter ++ first draft object pool intro +~ 3 hours + +08/10 ++ write first draft of intro chapter ++ simple navigation bar in generated html + + link to each generated page + + basic stylesheet +- stretch: outline object pool chapter + - research it online (take notes!) + - outline +~ 4 hours + +08/09 ++ create a hello world pattern chapter ++ figure out a format for chapters + + markdown ++ create a stylesheet + . base off existing one from ruby stuff ++ get a hello world in c++ working ++ get code inclusion working ++ clean up directories ++ commit ++ figure out a format for the main book outline + ? separate html pages for each chapter? ++ outline the introduction +~ 3 hours diff --git a/note/print workflow.txt b/note/print workflow.txt new file mode 100644 index 0000000..476a09c --- /dev/null +++ b/note/print workflow.txt @@ -0,0 +1,21 @@ +- Export to XML +- Import XML +- Place it in main text flow +- Untag everything and remove the story structure +- Pull out asides and restyle +- Change first paragraph body text styles +- Set chapter title and number on first page +- Update section in running footer +- Clean up + - Make sure there aren't bits of 14.4pt line height + - Format bullet lists + - Including first and last items +- Make sure italics in headers are correct +- Add illustrations + - Make 600 dpi bitmap + - Save to Print/Illustrations/... + - Place + - Make inline anchor + - Set Position to "Above Line" with 9pts of space before and 13.5pts after + - Add a caption +- Position asides \ No newline at end of file diff --git a/note/publishing.txt b/note/publishing.txt new file mode 100644 index 0000000..357c1a4 --- /dev/null +++ b/note/publishing.txt @@ -0,0 +1,84 @@ +- Purchase 10 ISBNs + Will need one for print and one for ebook, and 10 costs as much as two +- Create account on Amazon Author Central + Need to have book on Amazon first +- Write description for book +- Publisher Genever/Benning + +--- + +leanpub.com +gumroad +e-junkie +lulu +blurb +https://www.softcover.io/ +smashwords + +https://news.ycombinator.com/item?id=6052075 +https://news.ycombinator.com/item?id=7030112 +https://news.ycombinator.com/item?id=632104 +https://www.goodreads.com/topic/show/849429-lulu-vs-createspace +http://johnplogsdon.blogspot.com/2013/04/physical-books-createspace-vs-lulu-vs.html + +--- + +My advice to anyone else considering self-publishing: +* Start with Amazon KDP and CreateSpace. Both services are easy and fast. It's the quickest way to test an idea. +* Be prepared to iterate quickly based on reader feedback. +* Consider paying $100 or $200 for a decent cover. You can find designers lurking around the online writing communities (such as kboards.com for fiction) or hire someone on oDesk. Make sure they have experience designing book covers, which will save time and frustration. +* Have someone proof your manuscript. I see lots of writers who skip this step, and suffer in the ratings and reviews as a result. +* Have a cover blurb and Amazon description that grabs people. Also, make sure that readers can easily find out about you, either through the product listing page (which Amazon grabs from Amazon Author Central) or your own product website. +* If you want to use other platforms, Apple's iBookstore seems most promising. It's hard to set up, though. "iTunes Producer" is a very rough piece of software. However, if you've worked on iOS apps in the past at least you will be familiar with iTunes Connect, which is used to set pricing and monitor sales. +* I have sold many PDFs, but I am not sure how that would work for fiction. I started with e-junkie but switched to Gumroad (3) which has a much better interface. +Marketing is tough. One thing you can do once you have a print version through CreateSpace or another service, join Goodreads (a social network for people who love to read) and set up a Goodreads Giveaway (4) (a contest for your book that Goodreads runs -- usually a few hundred people sign up, and you have to send out 10 or 20 copies to winners that GR selects). It's free to set up, but you'll have to purchase and send out copies of the book to the winners of the giveaway. The advantages of this: Readers often write reviews, which are seen by other GR members. Many other people will put the book on their "to-read" list, and some will go out and buy the book right away because they don't want to wait to see if they won a copy. +Good luck! +1. http://www.digitalmediamachine.com/2012/09/do-people-judge-e... +2. http://in30minutes.com/ +3. https://gumroad.com/ +4. http://goodreads.com/giveaway + +http://snook.ca/archives/writing/selling-ebook-on-amazon +http://apethebook.com/ + +--- + +Spent some time looking into using leanpub.com for generating the epub and mobi +versions. It's pretty simple and works pretty well, if a bit slow. The landing +page is nice. + +The biggest problem is that the output doesn't look that great. The stylsheet +doesn't handle asides and large bullet points very well. They don't currently +give you any way to customize this, and if I hack it myself, I can't sell it +through them. + +Q: Does that matter? + +It may be simpler just to create the epub archive myself since I'm already +doing much of the work for it already. + + +--- + +Looked into being able to sell the epub and mobi files directly from my site. +It sounds nice in theory, but I think it's not worth the trouble. Pros: + +- More money since a publisher won't take a cut. +- People can get the book right from the site. + +Cons: + +- Technically complex to set up. +- Many users don't know what to do with a raw downloaded .epub or .mobi file. + Marketplaces like Kindle and iBooks handle getting it into the user's + device. +- May cannibalize my sales on those marketplaces. That in turn could lower the + book's ranking there. + +All of those cons are serious, but I think the last point is the real nail in +the coffin. Instead, I think I'll just have links to buy the book from a few +different marketplaces. + +--- + +http://www.hxa.name/articles/content/epub-guide_hxa7241_2007.html diff --git a/note/references.txt b/note/references.txt new file mode 100644 index 0000000..7c5c879 --- /dev/null +++ b/note/references.txt @@ -0,0 +1,9 @@ +http://gamadu.com/artemis/ +http://html5quintus.com + +http://eventuallyconsistent.net/2013/08/12/messaging-as-a-programming-model-part-1/ +http://research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf + +angel 2d + +http://entity-systems.wikidot.com/ \ No newline at end of file diff --git a/note/style.markdown b/note/style.markdown new file mode 100644 index 0000000..9f20f30 --- /dev/null +++ b/note/style.markdown @@ -0,0 +1,125 @@ +## Prose + +- `##` headers are title caps. +- `###` subheaders are sentence caps. +- "You" refers to the programmer. +- The player is genderless and referred to as "they". +- Ownership is expressed as "owner's thing" rather than "thing of the owner". +- Contractions are fine if it helps read conversationally. +- Keyboard keys have title case and are written as full words e.g. Control-Z. +- Code blocks are preceded by a colon. +- Bullet points may be sentence fragments but start with a cap and end with a period. +- The "See Also" section consists entirely of bullet points. +- Commas before conjunctions are fine. +- American spelling. +- References to class name, methods or any code use code format. +- Italics for emphasis. +- Quote block used for sentence long quotes but double quote ("") used for references what other people call something e.g. GoF call this the "subject". +- Colon precedes block quote. +- Punctuation appears outside quotation marks unless the punctuation is part of the quoted matter (logical punctuation). +- Items in a numbered list are complete sentences with starting caps and ending period. +- Use "Naïve" instead of "Naive". +- Use smart quotes. +- Use ` -- ` for em dashes. +- Prefer present tense instead of future. +- Prefer "we" over "you" or "I". +- Use *O(n²)* for Big-O notation, not `O(n^2)`. +- Put class name in `CodeFont` when referring to the class itself. +- It's "Foo pattern", not "Foo Pattern", and the link just surrounds, "Foo". + Ex: "Add a method to `Foo`." +- Use normal font and lowercase when referring to objects of the class. + Ex: "Create a new foo and pass it to `bar()`." +- Capitalize when referring to pattern like proper noun, lowercase when + referring to object implementing pattern. + Ex: "Object Pool is a great pattern. An object pool will save you memory." +- Design Patterns and other book titles are *italicized*. +- Can end sentence before illustration with ":". +- "vtable", all lowercase, no hyphen. +- "on-screen" when an adjective ("the on-screen player"), "on screen" when a + place ("the player is on screen"). +- Don't use a numbered list unless the order matters. +- Do not have a subheader immediately follow a header + +## Code + +- Class and enum names are Pascal case. +- Camel case method names and properties. +- Trailing underscore for fields. That way isFoo() doesn't collide with isFoo + field. +- No "I" for interfaces. +- Do use "virtual" for overridden methods. +- Use "double" for floating point variables. +- If base class has virtual methods, give it a virtual destructor. +- Virtual destructor comes before other definitions. +- Virtual destructor {} is on the same line as the signature. +- Public before private. +- Last entry in enum doesn't have comma. +- Cases are vertically aligned if the code is one statement long. +- Each property in an initializer list gets its own line. +- The empty constructor code after initializer list gets its own line and vertically aligns with initializer list colon. +- Function code and signature on the same line is fine if code is one line long. +- Passing mutable references is fine. +- Prefer postfix increment. + +## Open Questions + +- Q: How are switch cases indented? +- Q: How much should const be used? +- Q: Why use ":" before code blocks but not illustrations? +- Q: Use parentheses when referring to methods/functions? `foo` or `foo()`? +- Q: `true` or true? +- Q: Are "enum", "struct", "int", etc. normal words or `code font`? +- Q: Hide fields behind getters (better style) or allow public fields (less + boilerplate)? + +## Pattern Chapter Structure + +Each of the pattern chapters outside of the ones in "Design Patterns Revisited" has the same structure: + +### Intent + +This is a single italicized, imperative sentence explaining the core idea behind the pattern and the problem it solves. + +### Motivation + +This is the most narrative part of the chapter. It walks through an example problem and builds up to how the pattern solves it. It may have subheadings, illustrations, and even some code samples. It tends to be pretty large. + +### The Pattern + +The motivation section culminates in an explanation of the pattern and how it solves the previous problem. This one- or two-paragraph section summarizes that. + +It's written in present tense with the components of the pattern itself as subjects. The major pieces of the pattern are in **bold face** the first time they are introduced here. + +### When to Use It + +Now that the reader sees how the pattern is useful for one problem, this short section describes which other problems are or are not a good fit for it. + +It's usually only a few paragraphs long and often ends with a bullet list. It does not have any subheadings. + +### Keep in Mind + +This expands on the previous section and describes consequences -- mostly negative -- of using the pattern. If it's short, it will just be a few paragraphs with no subheaders. When longer, it will have a single paragraph followed by a few subheaders, one for each consequence. + +### Sample Code + +This long section walks step by step through a full implementation of the pattern. It will have paragraphs, illustrations, subheaders, and lots of code. + +### Design Decisions + +This shows how the pattern can vary along different axes within the solution space. It has a one paragraph intro followed be a series of design decisions. + +Each has a subheader in the form of a question. It may be followed by a few paragraphs explaining the question. + +After that, a bullet list covers some possible answers to that question. Each bullet in the list starts with a one sentence answer in **bold face**, ending with a colon. + +Then there is a subsequent couple of paragraphs explaining the answer. This may be omitted. + +Often, there will then be a *nested* bullet list with some ramifications of that decision. If so, each point will be one or more paragraphs, the first of which starts with an italicized sentence. + +There may be code samples or illustrations anywhere in here. + +Some of the decision question subsections don't follow this structure. They still have a subheader in the form of a question, but the body may just be prose or follow some other format. + +### See Also + +The easiest section. It's just a bullet list of short paragraphs containing links to other resources. diff --git a/note/todo.txt b/note/todo.txt new file mode 100644 index 0000000..42edaf1 --- /dev/null +++ b/note/todo.txt @@ -0,0 +1,4 @@ +- Fix prose references to hyperlinks to make them make sense +- Index + - Go through indexes for Intro - Singleton and use multi-page references + where appropriate diff --git a/script/format.py b/script/format.py new file mode 100644 index 0000000..f833f00 --- /dev/null +++ b/script/format.py @@ -0,0 +1,501 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- + +# Converts from the source markup format to HTML for the web version. + +import sys +reload(sys) +sys.setdefaultencoding('utf8') + +import glob +import os +import re +import subprocess +import sys +import time +from datetime import datetime + +import markdown +import smartypants + +# Assumes cwd is root project dir. + +GREEN = '\033[32m' +RED = '\033[31m' +DEFAULT = '\033[0m' +PINK = '\033[91m' +YELLOW = '\033[33m' + +CHAPTERS = [ + "Acknowledgements", + "Introduction", + "Architecture, Performance, and Games", + "Design Patterns Revisited", + "Command", + "Flyweight", + "Observer", + "Prototype", + "Singleton", + "State", + "Sequencing Patterns", + "Double Buffer", + "Game Loop", + "Update Method", + "Behavioral Patterns", + "Bytecode", + "Subclass Sandbox", + "Type Object", + "Decoupling Patterns", + "Component", + "Event Queue", + "Service Locator", + "Optimization Patterns", + "Data Locality", + "Dirty Flag", + "Object Pool", + "Spatial Partition" +] + +# URLs for hyperlinks to chapters. Note that the order is significant here. +# The index in this list + 1 is the chapter's number in the table of contents. +CHAPTER_HREFS = [ + "acknowledgements.html", + "introduction.html", + "architecture-performance-and-games.html", + "command.html", + "flyweight.html", + "observer.html", + "prototype.html", + "singleton.html", + "state.html", + "double-buffer.html", + "game-loop.html", + "update-method.html", + "bytecode.html", + "subclass-sandbox.html", + "type-object.html", + "component.html", + "event-queue.html", + "service-locator.html", + "data-locality.html", + "dirty-flag.html", + "object-pool.html", + "spatial-partition.html" +] + +num_chapters = 0 +empty_chapters = 0 +total_words = 0 + +extension = "html" + +def output_path(pattern): + return extension + "/" + pattern + "." + extension + + +def cpp_path(pattern): + return 'code/cpp/' + pattern + '.h' + + +def pretty(text): + '''Use nicer HTML entities and special characters.''' + text = text.replace(" -- ", " — ") + text = text.replace("à", "à") + text = text.replace("ï", "ï") + text = text.replace("ø", "ø") + text = text.replace("æ", "æ") + return text + + +def format_file(path, nav, skip_up_to_date): + basename = os.path.basename(path) + basename = basename.split('.')[0] + + # See if the HTML is up to date. + sourcemod = os.path.getmtime(path) + sourcemod = max(sourcemod, os.path.getmtime('asset/template.html')) + if os.path.exists(cpp_path(basename)): + sourcemod = max(sourcemod, os.path.getmtime(cpp_path(basename))) + + destmod = 0 + if os.path.exists(output_path(basename)): + destmod = max(destmod, os.path.getmtime(output_path(basename))) + + if skip_up_to_date and sourcemod < destmod: + return + + title = '' + title_html = '' + section = '' + isoutline = False + + navigation = [] + + # Read the markdown file and preprocess it. + contents = '' + with open(path, 'r') as input: + # Read each line, preprocessing the special codes. + for line in input: + stripped = line.lstrip() + indentation = line[:len(line) - len(stripped)] + + if stripped.startswith('^'): + command,_,args = stripped.rstrip('\n').lstrip('^').partition(' ') + args = args.strip() + + if command == 'title': + title = args + title_html = title + + # Remove any discretionary hyphens from the title. + title = title.replace('­', '') + elif command == 'section': + section = args + elif command == 'code': + contents = contents + include_code(basename, args, indentation) + elif command == 'outline': + isoutline = True + else: + print "UNKNOWN COMMAND:", command, args + + elif extension != "xml" and stripped.startswith('#'): + # Build the page navigation from the headers. + index = stripped.find(" ") + headertype = stripped[:index] + header = pretty(stripped[index:].strip()) + anchor = header.lower().replace(' ', '-') + anchor = anchor.translate(None, '.?!:/"') + + # Add an anchor to the header. + contents += indentation + headertype + contents += '' + header + '\n' + + # Build the navigation. + if len(headertype) == 2: + navigation.append((len(headertype), header, anchor)) + + else: + contents += pretty(line) + + modified = datetime.fromtimestamp(os.path.getmtime(path)) + mod_str = modified.strftime('%B %d, %Y') + + with open("asset/template." + extension) as f: + template = f.read() + + # Write the output. + with open(output_path(basename), 'w') as out: + title_text = title + section_header = "" + + if section != "": + title_text = title + " · " + section + section_href = section.lower().replace(" ", "-") + section_header = '{}'.format( + section_href, section) + + prev_link, next_link = make_prev_next(title) + + contents = contents.replace(')", output) + + def clean_up_code_xml(code): + # Ditch most code formatting tags. + code = re.sub(r'([^<]+)', + r"\2", code) + + # Turn comments into something InDesign can map to a style. + code = re.sub(r'([^<]+)', + r"\2", code) + + # Turn newlines into soft returns so code blocks stay one paragraph, except + # for the last one. + code = code[:-1].replace("\n", "
") + "\n" + + return code + + def fix_link(match): + tag = match.group(1) + contents = match.group(2) + href = re.search(r'href\s*=\s*"([^"]+)"', tag).group(1) + + # If it's not a link to a chapter, just return the contents of the link and + # strip out the link itself. + if not href in CHAPTER_HREFS: + return contents + + # Turn it into a chapter number reference. + return "{} ({})".format( + contents, CHAPTER_HREFS.index(href) + 1) + + def clean_up_xhtml(html): + # Replace chapter links with chapter number references and remove other + # links. + html = re.sub(r"]+)>([^<]+)", fix_link, html) + + # Ditch newlines in the middle of blocks of text. Out of sheer malice, + # even though they are meaningless in actual XML, InDesign treats them + # as significant. + html = re.sub(r"\n(? <", "><") + + # Re-add newlines after closing paragraph-level tags. + html = html.replace("

", "

\n") + html = html.replace("", "\n") + html = html.replace("", "\n") + html = html.replace("", "\n") + html = html.replace("", "\n") + html = html.replace("", "\n") + html = html.replace("", "\n") + html = html.replace("", "\n") + html = html.replace("", "\n") + + # TODO: Non-breaking spaces in ... sections. + return html + + result = "" + for chunk in chunks: + if chunk == "
":
+      in_code = True
+      result += chunk
+    elif chunk == "
": + in_code = False + result += chunk + else: + if in_code: + result += clean_up_code_xml(chunk) + else: + result += clean_up_xhtml(chunk) + + return result + + +def title_to_file(title): + """Given a title like "Event Queue", converts it to the corresponding file + name like "event-queue".""" + + return (title.lower() + .replace(" ", "-") + .replace(",", "")) + + +def make_prev_next(title): + """Generate the links that thread through the chapters.""" + chapter_index = CHAPTERS.index(title) + prev_link = "" + next_link = "" + if chapter_index > 0: + prev_href = title_to_file(CHAPTERS[chapter_index - 1]) + prev_link = 'Previous Chapter'.format( + prev_href, CHAPTERS[chapter_index - 1]) + + if chapter_index < len(CHAPTERS) - 1: + next_href = title_to_file(CHAPTERS[chapter_index + 1]) + next_link = 'Next Chapter'.format( + next_href, CHAPTERS[chapter_index + 1]) + + return (prev_link, next_link) + + +def navigation_to_html(chapter, headers): + nav = '' + + # Section headers start two levels deep. + currentdepth = 1 + for depth, header, anchor in headers: + if currentdepth == depth: + nav += '
  • \n' + + while currentdepth < depth: + nav += '
    • \n' + currentdepth += 1 + + while currentdepth > depth: + nav += '
    \n' + currentdepth -= 1 + + nav += '' + header + '' + + + # Close the lists. + while currentdepth > 1: + nav += '
  • \n' + currentdepth -= 1 + + return nav + + +def include_code(pattern, index, indentation): + with open(cpp_path(pattern), 'r') as source: + lines = source.readlines() + + code = indentation + ' :::cpp\n' + inblock = False + omitting = False + omitting_name = False + blockindent = 0 + + for line in lines: + stripped = line.strip() + + if inblock: + if stripped == '//^' + index: + # End of our block. + break + + elif stripped == '//^omit': + omitting = not omitting + + elif stripped == '//^omit ' + index: + # Omitting a section just for this block. Other blocks that + # Contain this code may not omit it. + omitting_name = not omitting_name + + elif stripped.startswith('//^'): + # A code block comment for another block, + # so just ignore it. This can occur with + # nested code blocks. + pass + + elif not omitting and not omitting_name: + # Hackish. Can't strip the leading indent off of blank + # lines. + if stripped == '': + code += '\n' + else: + code_line = line[blockindent:] + if len(code_line) > 64: + print "Warning long source line ({} chars):\n{}".format( + len(code_line), code_line) + code += indentation + ' ' + code_line + + else: + if stripped == '//^' + index: + inblock = True + blockindent = len(line) - len(line.lstrip()) + + return code + + +def buildnav(searchpath): + nav = '' + + return nav + + +def format_files(file_filter, skip_up_to_date): + '''Process each markdown file.''' + for f in glob.iglob(searchpath): + if file_filter == None or file_filter in f: + format_file(f, nav, skip_up_to_date) + + +def check_sass(): + sourcemod = os.path.getmtime('asset/style.scss') + destmod = os.path.getmtime('html/style.css') + if sourcemod < destmod: + return + + subprocess.call(['sass', 'asset/style.scss', 'html/style.css']) + print "{}✓{} style.css".format(GREEN, DEFAULT) + + +searchpath = ('book/*.markdown') + +nav = buildnav(searchpath) + +if len(sys.argv) == 2 and sys.argv[1] == '--watch': + while True: + format_files(None, True) + check_sass() + time.sleep(0.3) +else: + if len(sys.argv) > 1 and sys.argv[1] == '--xml': + extension = "xml" + del sys.argv[1] + + # Can specify a file name filter to just regenerate a subset of the files. + file_filter = None + if len(sys.argv) > 1: + file_filter = sys.argv[1] + + format_files(file_filter, False) + + average_word_count = total_words / (num_chapters - empty_chapters) + estimated_word_count = total_words + (empty_chapters * average_word_count) + percent_finished = total_words * 100 / estimated_word_count + + print "{}/~{} words ({}%)".format( + total_words, estimated_word_count, percent_finished) diff --git a/watch b/watch new file mode 100644 index 0000000..4b65ab0 --- /dev/null +++ b/watch @@ -0,0 +1,2 @@ +#!/bin/bash +python script/format.py --watch $@