PageSourceSearch

https://jerrygreen.vercel.app/blog

html jerrygreen.vercel.app collected 2026-10-03 07:08:44 UTC 132,516 bytes, 83 lines download raw bytes

1<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, shrink-to-fit=no, viewport-fit=cover, minimum-scale=1.0, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui"/><link rel="manifest" href="/manifest.json"/><meta name="application-name" content="JerryGreen&#x27;s Blog"/><meta name="apple-mobile-web-app-capable" content="yes"/><meta name="apple-mobile-web-app-status-bar-style" content="default"/><meta name="apple-mobile-web-app-title" content="JerryGreen&#x27;s Blog"/><meta name="format-detection" content="telephone=no"/><meta name="mobile-web-app-capable" content="yes"/><meta name="theme-color" content="#ffffff"/><link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png"/>
vendor: 66 bytes, line 1
1<script async="" src="https://www.googletagmanager.com/gtag/js?id=
1UA-137515166-2
vendor: 11 bytes, line 1
1"></script>
1<script>
vendor: 127 bytes, lines 2-5
2window.dataLayer = window.dataLayer || [];
3function gtag(){dataLayer.push(arguments);}
4gtag('js', new Date());
5gtag('config', '
5UA-137515166-2
vendor: 64 bytes, lines 5-8
5', {
6  page_path: window.location.pathname,
7});
8                
8</script>
8<title>JerryGreen&#x27;s Blog</title><meta name="description" content="Posts written by Jerry Green, a software engineer."/><meta name="next-head-count" content="17"/><link rel="preload" href="/_next/static/css/169bab14daae91e470ed.css" as="style"/><link rel="stylesheet" href="/_next/static/css/169bab14daae91e470ed.css" data-n-g=""/><link rel="preload" href="/_next/static/css/21b26dfae91df211b709.css" as="style"/><link rel="stylesheet" href="/_next/static/css/21b26dfae91df211b709.css" data-n-p=""/><noscript data-n-css=""></noscript><link rel="preload" href="/_next/static/chunks/main-2202f484d6914ceb4d8e.js" as="script"/><link rel="preload" href="/_next/static/chunks/webpack-50bee04d1dc61f8adf5b.js" as="script"/><link rel="preload" href="/_next/static/chunks/framework.8dfdf69b72dfc2029ab9.js" as="script"/><link rel="preload" href="/_next/static/chunks/8f79dc18.7f06bc6c2c3f10c57bd0.js" as="script"/><link rel="preload" href="/_next/static/chunks/commons.b78e9ab41aa75bdf260d.js" as="script"/><link rel="preload" href="/_next/static/chunks/5fe5d2636d30c84a90ce481a26ac21e9514f7953.8221c3accfa506bee6c0.js" as="script"/><link rel="preload" href="/_next/static/chunks/pages/_app-333ec2a757a7f61002ec.js" as="script"/><link rel="preload" href="/_next/static/chunks/0e226fb0.00458a403201668feaaf.js" as="script"/><link rel="preload" href="/_next/static/chunks/134b6bfdd40f1c0473727571e3cbe8fd291287bb.cc4fa7855a83014ee0b1.js" as="script"/><link rel="preload" href="/_next/static/chunks/2798d23c72fc1fa6b330024ccd47ee9972611cff.0192114cbb8db4c687e0.js" as="script"/><link rel="preload" href="/_next/static/chunks/2abca662105608a5fed85b4aa2c3215d764b09cb.b6715c3567e61bb74677.js" as="script"/><link rel="preload" href="/_next/static/chunks/4fe3a15990720369bf83f599385448bc626ca7a2.412b0268891fc4011179.js" as="script"/><link rel="preload" href="/_next/static/chunks/pages/blog-1da48ee7703faa2f5fbd.js" as="script"/><style id="__jsx-1616313291">i.jsx-1616313291{display:inline;line-height:26px;}</style><style id="__jsx-3762511973">article.jsx-3762511973{margin-top:36px;clear:left;}article.withPreview.jsx-3762511973{min-height:160px;}article.jsx-3762511973 h2{line-height:26px;}article.jsx-3762511973:first-child{margin-top:0;}.link.jsx-3762511973{display:block;}.link.jsx-3762511973 img.jsx-3762511973{position:relative;border-radius:12px;margin-right:12px;width:128px;height:78px;object-fit:cover;margin-bottom:6px;margin-top:auto;margin-bottom:auto;vertical-align:middle;}@media screen and (min-width:544px){.link.jsx-3762511973 img.jsx-3762511973{width:256px;height:160px;}}.link.jsx-3762511973:hover{-webkit-text-decoration:none;text-decoration:none;}</style><style data-styled="" data-styled-version="5.2.1">.kOTcwk{height:56px;}/*!sc*/
9.cNnYGn{margin-left:auto;margin-right:auto;width:100%;height:56px;}/*!sc*/
10@media screen and (min-width:544px){.cNnYGn{width:100%;}}/*!sc*/
11@media screen and (min-width:768px){.cNnYGn{width:82.39999999999999%;}}/*!sc*/
12@media screen and (min-width:1012px){.cNnYGn{width:46.35%;}}/*!sc*/
13@media screen and (min-width:1280px){.cNnYGn{width:38.2%;}}/*!sc*/
14.eFjTaX{margin-left:auto;margin-right:auto;padding-left:24px;padding-right:24px;width:100%;}/*!sc*/
15@media screen and (min-width:544px){.eFjTaX{padding-left:24px;padding-right:24px;}}/*!sc*/
16@media screen and (min-width:768px){.eFjTaX{padding-left:24px;padding-right:24px;}}/*!sc*/
17@media screen and (min-width:1012px){.eFjTaX{padding-left:0;padding-right:0;}}/*!sc*/
18@media screen and (min-width:544px){.eFjTaX{width:100%;}}/*!sc*/
19@media screen and (min-width:768px){.eFjTaX{width:82.39999999999999%;}}/*!sc*/
20@media screen and (min-width:1012px){.eFjTaX{width:46.35%;}}/*!sc*/
21@media screen and (min-width:1280px){.eFjTaX{width:38.2%;}}/*!sc*/
22.heZGbK{margin-left:auto;margin-right:auto;width:100%;}/*!sc*/
23@media screen and (min-width:544px){.heZGbK{width:100%;}}/*!sc*/
24@media screen and (min-width:768px){.heZGbK{width:82.39999999999999%;}}/*!sc*/
25@media screen and (min-width:1012px){.heZGbK{width:46.35%;}}/*!sc*/
26@media screen and (min-width:1280px){.heZGbK{width:38.2%;}}/*!sc*/
27data-styled.g3[id="Box-sc-1b6inku-0"]{content:"kOTcwk,cNnYGn,fJORUp,eFjTaX,heZGbK,"}/*!sc*/
28.FgSRy{top:0;z-index:1;position:-webkit-sticky;position:sticky;}
28/*!sc*/
29data-styled.g7[id="Position-gj4o9g-0"]{content:"FgSRy,"}/*!sc*/
30.hMVpnB{font-weight:600;font-size:32px;margin:0;font-size:24px;}/*!sc*/
31data-styled.g44[id="Heading-sc-1m35ws5-0"]{content:"hMVpnB,"}/*!sc*/
32.dkDfm{position:relative;padding-top:24px;padding-bottom:24px;margin-bottom:24px;border-bottom:1px solid #e1e4e8;}/*!sc*/
33data-styled.g48[id="Pagehead-r7ayx1-0"]{content:"dkDfm,"}/*!sc*/
34.edovIp{position:relative;}/*!sc*/
35.edovIp::before{position:absolute;z-index:1000001;display:none;width:0px;height:0px;color:#1b1f23;pointer-events:none;content:'';border:6px solid transparent;opacity:0;}/*!sc*/
36.edovIp::after{position:absolute;z-index:1000000;display:none;padding:0.5em 0.75em;font:normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";-webkit-font-smoothing:subpixel-antialiased;color:#fff;text-align:center;-webkit-text-decoration:none;text-decoration:none;text-shadow:none;text-transform:none;-webkit-letter-spacing:normal;-moz-letter-spacing:normal;-ms-letter-spacing:normal;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:#1b1f23;border-radius:3px;opacity:0;}/*!sc*/
37@-webkit-keyframes tooltip-appear{from{opacity:0;}to{opacity:1;}}/*!sc*/
38@keyframes tooltip-appear{from{opacity:0;}to{opacity:1;}}/*!sc*/
39.edovIp:hover::before,.edovIp:active::before,.edovIp:focus::before,.edovIp:hover::after,.edovIp:active::after,.edovIp:focus::after{display:inline-block;-webkit-text-decoration:none;text-decoration:none;-webkit-animation-name:tooltip-appear;animation-name:tooltip-appear;-webkit-animation-duration:0.1s;animation-duration:0.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-animation-delay:0.4s;animation-delay:0.4s;}/*!sc*/
40.edovIp.tooltipped-no-delay:hover::before,.edovIp.tooltipped-no-delay:active::before,.edovIp.tooltipped-no-delay:focus::before,.edovIp.tooltipped-no-delay:hover::after,.edovIp.tooltipped-no-delay:active::after,.edovIp.tooltipped-no-delay:focus::after{-webkit-animation-delay:0s;animation-delay:0s;}/*!sc*/
41.edovIp.tooltipped-multiline:hover::after,.edovIp.tooltipped-multiline:active::after,.edovIp.tooltipped-multiline:focus::after{display:table-cell;}/*!sc*/
42.edovIp.tooltipped-s::after,.edovIp.tooltipped-se::after,.edovIp.tooltipped-sw::after{top:100%;right:50%;margin-top:6px;}/*!sc*/
43.edovIp.tooltipped-s::before,.edovIp.tooltipped-se::before,.edovIp.tooltipped-sw::before{top:auto;right:50%;bottom:-7px;margin-right:-6px;border-bottom-color:#1b1f23;}/*!sc*/
44.edovIp.tooltipped-se::after{right:auto;left:50%;margin-left:-16px;}/*!sc*/
45.edovIp.tooltipped-sw::after{margin-right:-16px;}/*!sc*/
46.edovIp.tooltipped-n::after,.edovIp.tooltipped-ne::after,.edovIp.tooltipped-nw::after{right:50%;bottom:100%;margin-bottom:6px;}/*!sc*/
47.edovIp.tooltipped-n::before,.edovIp.tooltipped-ne::before,.edovIp.tooltipped-nw::before{top:-7px;right:50%;bottom:auto;margin-right:-6px;border-top-color:#1b1f23;}/*!sc*/
48.edovIp.tooltipped-ne::after{right:auto;left:50%;margin-left:-16px;}/*!sc*/
49.edovIp.tooltipped-nw::after{margin-right:-16px;}/*!sc*/
50.edovIp.tooltipped-s::after,.edovIp.tooltipped-n::after{-webkit-transform:translateX(50%);-ms-transform:translateX(50%);transform:translateX(50%);}/*!sc*/
51.edovIp.tooltipped-w::after{right:100%;bottom:50%;margin-right:6px;-webkit-transform:translateY(50%);-ms-transform:translateY(50%);transform:translateY(50%);}/*!sc*/
52.edovIp.tooltipped-w::before{top:50%;bottom:50%;left:-7px;margin-top:-6px;border-left-color:#1b1f23;}/*!sc*/
53.edovIp.tooltipped-e::after{bottom:50%;left:100%;margin-left:6px;-webkit-transform:translateY(50%);-ms-transform:translateY(50%);transform:translateY(50%);}/*!sc*/
54.edovIp.tooltipped-e::before{top:50%;right:-7px;bottom:50%;margin-top:-6px;border-right-color:#1b1f23;}/*!sc*/
55.edovIp.tooltipped-multiline::after{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:250px;word-wrap:break-word;white-space:pre-line;border-collapse:separate;}/*!sc*/
56.edovIp.tooltipped-multiline.tooltipped-s::after,.edovIp.tooltipped-multiline.tooltipped-n::after{right:auto;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);}/*!sc*/
57.edovIp.tooltipped-multiline.tooltipped-w::after,.edovIp.tooltipped-multiline.tooltipped-e::after{right:100%;}/*!sc*/
58.edovIp.tooltipped-align-right-2::after{right:0;margin-right:0;}/*!sc*/
59.edovIp.tooltipped-align-right-2::before{right:15px;}/*!sc*/
60.edovIp.tooltipped-align-left-2::after{left:0;margin-left:0;}/*!sc*/
61.edovIp.tooltipped-align-left-2::before{left:10px;}/*!sc*/
62data-styled.g83[id="Tooltip-h3c7f-0"]{content:"edovIp,"}/*!sc*/
63.bHhrhM{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;
63-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;border-bottom:1px solid #eaecef;}/*!sc*/
64.bHhrhM.UnderlineNav--right{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;}/*!sc*/
65.bHhrhM.UnderlineNav--right .UnderlineNav-item{margin-right:0;margin-left:16px;}/*!sc*/
66.bHhrhM.UnderlineNav--right .UnderlineNav-actions{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;}/*!sc*/
67.bHhrhM.UnderlineNav--full{display:block;}/*!sc*/
68.bHhrhM .UnderlineNav-body{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin-bottom:-1px;}/*!sc*/
69.bHhrhM .UnderlineNav-actions{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;}/*!sc*/
70data-styled.g85[id="UnderlineNav-sc-14g1rj0-0"]{content:"bHhrhM,"}/*!sc*/
71.eSYbnG{padding:16px 8px;margin-right:16px;font-size:14px;line-height:1.5;color:#586069;text-align:center;border-bottom:2px solid transparent;-webkit-text-decoration:none;text-decoration:none;}/*!sc*/
72.eSYbnG:hover,.eSYbnG:focus{color:#24292e;-webkit-text-decoration:none;text-decoration:none;border-bottom-color:#e1e4e8;-webkit-transition:0.2s ease;transition:0.2s ease;}/*!sc*/
73.eSYbnG:hover .UnderlineNav-octicon,.eSYbnG:focus .UnderlineNav-octicon{color:#6a737d;}/*!sc*/
74.eSYbnG.selected{color:#24292e;border-bottom-color:#f66a0a;}/*!sc*/
75.eSYbnG.selected .UnderlineNav-octicon{color:#6a737d;}/*!sc*/
76data-styled.g86[id="UnderlineNav__Link-sc-14g1rj0-1"]{content:"eSYbnG,"}/*!sc*/
77</style></head><body><div id="__next"><header width="1,1,0.824,0.4635,0.382" class="Box-sc-1b6inku-0 cNnYGn Box-sc-1b6inku-0 Position-gj4o9g-0 kOTcwk FgSRy Header_Header__1_aqX" height="56px"><nav aria-label="Settings" class="Box-sc-1b6inku-0 fJORUp UnderlineNav-sc-14g1rj0-0 bHhrhM Settings_root__UriMV NavBlock_root__2_19z"><a href="/settings" class="UnderlineNav__Link-sc-14g1rj0-1 eSYbnG UnderlineNav-item"><svg aria-hidden="true" role="img" class="octicon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display:inline-block;user-select:none;vertical-align:text-bottom"><path fill-rule="evenodd" d="M7.429 1.525a6.593 6.593 0 011.142 0c.036.003.108.036.137.146l.289 1.105c.147.56.55.967.997 1.189.174.086.341.183.501.29.417.278.97.423 1.53.27l1.102-.303c.11-.03.175.016.195.046.219.31.41.641.573.989.014.031.022.11-.059.19l-.815.806c-.411.406-.562.957-.53 1.456a4.588 4.588 0 010 .582c-.032.499.119 1.05.53 1.456l.815.806c.08.08.073.159.059.19a6.494 6.494 0 01-.573.99c-.02.029-.086.074-.195.045l-1.103-.303c-.559-.153-1.112-.008-1.529.27-.16.107-.327.204-.5.29-.449.222-.851.628-.998 1.189l-.289 1.105c-.029.11-.101.143-.137.146a6.613 6.613 0 01-1.142 0c-.036-.003-.108-.037-.137-.146l-.289-1.105c-.147-.56-.55-.967-.997-1.189a4.502 4.502 0 01-.501-.29c-.417-.278-.97-.423-1.53-.27l-1.102.303c-.11.03-.175-.016-.195-.046a6.492 6.492 0 01-.573-.989c-.014-.031-.022-.11.059-.19l.815-.806c.411-.406.562-.957.53-1.456a4.587 4.587 0 010-.582c.032-.499-.119-1.05-.53-1.456l-.815-.806c-.08-.08-.073-.159-.059-.19a6.44 6.44 0 01.573-.99c.02-.029.086-.075.195-.045l1.103.303c.559.153 1.112.008 1.529-.27.16-.107.327-.204.5-.29.449-.222.851-.628.998-1.189l.289-1.105c.029-.11.101-.143.137-.146zM8 0c-.236 0-.47.01-.701.03-.743.065-1.29.615-1.458 1.261l-.29 1.106c-.017.066-.078.158-.211.224a5.994 5.994 0 00-.668.386c-.123.082-.233.09-.3.071L3.27 2.776c-.644-.177-1.392.02-1.82.63a7.977 7.977 0 00-.704 1.217c-.315.675-.111 1.422.363 1.891l.815.806c.05.048.098.147.088.294a6.084 6.084 0 000 .772c.01.147-.038.246-.088.294l-.815.806c-.474.469-.678 1.216-.363 1.891.2.428.436.835.704 1.218.428.609 1.176.806 1.82.63l1.103-.303c.066-.019.176-.011.299.071.213.143.436.272.668.386.133.066.194.158.212.224l.289 1.106c.169.646.715 1.196 1.458 1.26a8.094 8.094 0 001.402 0c.743-.064 1.29-.614 1.458-1.26l.29-1.106c.017-.066.078-.158.211-.224a5.98 5.98 0 00.668-.386c.123-.082.233-.09.3-.071l1.102.302c.644.177 1.392-.02 1.82-.63.268-.382.505-.789.704-1.217.315-.675.111-1.422-.364-1.891l-.814-.806c-.05-.048-.098-.147-.088-.294a6.1 6.1 0 000-.772c-.01-.147.039-.246.088-.294l.814-.806c.475-.469.679-1.216.364-1.891a7.992 7.992 0 00-.704-1.218c-.428-.609-1.176-.806-1.82
77-.63l-1.103.303c-.066.019-.176.011-.299-.071a5.991 5.991 0 00-.668-.386c-.133-.066-.194-.158-.212-.224L10.16 1.29C9.99.645 9.444.095 8.701.031A8.094 8.094 0 008 0zm1.5 8a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM11 8a3 3 0 11-6 0 3 3 0 016 0z"></path></svg></a></nav><nav aria-label="Navigation" class="Box-sc-1b6inku-0 fJORUp UnderlineNav-sc-14g1rj0-0 bHhrhM NavBlock_root__2_19z"><a href="/" class="UnderlineNav__Link-sc-14g1rj0-1 eSYbnG UnderlineNav-item">Me</a><a href="/blog" selected="" class="UnderlineNav__Link-sc-14g1rj0-1 eSYbnG UnderlineNav-item selected">Blog</a></nav></header><main width="1,1,0.824,0.4635,0.382" class="Box-sc-1b6inku-0 eFjTaX Main_root__26LeT"><article class="jsx-3762511973 withPreview"><a class="jsx-3762511973 link" href="/blog/procedural-world-generation"><img src="https://user-images.githubusercontent.com/13215662/116163528-5d81c380-a711-11eb-8763-ff4c61bff3f0.gif" title="Procedurally generated little world" alt="map_static" align="left" class="jsx-3762511973"/><h2 font-size="4" class="Heading-sc-1m35ws5-0 hMVpnB Title_heading__J32Ek" itemProp="title">Procedural World Generation</h2><i class="jsx-1616313291 Title_i__3IQ7t"><span aria-label="Published at 2021-05-14
78Edited at 2022-08-26" class="Tooltip-h3c7f-0 edovIp tooltipped-n"><svg aria-hidden="true" role="img" class="octicon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display:inline-block;user-select:none;vertical-align:text-bottom"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg> <time itemProp="datePublished" dateTime="2021-05-14" class="jsx-1616313291">May 14, 2021</time></span><span aria-label="It takes ~8 mins to read
79Based on 120 words per min" class="Tooltip-h3c7f-0 edovIp tooltipped-n"><svg aria-hidden="true" role="img" class="octicon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display:inline-block;user-select:none;vertical-align:text-bottom"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"></path></svg> <!-- -->8 min read</span></i></a><div><p>Recently, I wanted to learn to make generators that are able to create some small seamless worlds for a game. Long time ago I would think: how on earth people do that? Here&#x27;s what I came up with now...</p></div></article><article class="jsx-3762511973 "><a class="jsx-3762511973 link" href="/blog/making-custom-game-engine"><h2 font-size="4" class="Heading-sc-1m35ws5-0 hMVpnB Title_heading__J32Ek" itemProp="title">Making Custom Game Engine</h2><i class="jsx-1616313291 Title_i__3IQ7t"><span aria-label="Published at 2021-03-27
80Edited at 2022-03-25" class="Tooltip-h3c7f-0 edovIp tooltipped-n"><svg aria-hidden="true" role="img" class="octicon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display:inline-block;user-select:none;vertical-align:text-bottom"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg> <time itemProp="datePublished" dateTime="2021-03-27" class="jsx-1616313291">Mar 27, 2021</time></span><span aria-label="It takes ~11 mins to read
81Based on 120 words per min" class="Tooltip-h3c7f-0 edovIp tooltipped-n"><svg aria-hidden="true" role="img" class="octicon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display:inline-block;user-select:none;vertical-align:text-bottom"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"></path></svg> <!-- -->11 min read</span></i></a><div><p>In this article I&#x27;ll cover some in-depth benchmarks of what I built and play around it a bit.</p></div></article><article class="jsx-3762511973 "><a class="jsx-3762511973 link" href="/blog/beginning-game-development"><h2 font-size="4" class="Heading-sc-1m35ws5-0 hMVpnB Title_heading__J32Ek" itemProp="title">Beginning Game Development with TypeScript and ECS</h2><i class="jsx-1616313291 Title_i__3IQ7t"><span aria-label="Published at 2021-03-25
82Edited at 2021-09-21" class="Tooltip-h3c7f-0 edovIp tooltipped-n"><svg aria-hidden="true" role="img" class="octicon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display:inline-block;user-select:none;vertical-align:text-bottom"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg> <time itemProp="datePublished" dateTime="2021-03-25" class="jsx-1616313291">Mar 25, 2021</time></span><span aria-label="It takes ~10 mins to read
83Based on 120 words per min" class="Tooltip-h3c7f-0 edovIp tooltipped-n"><svg aria-hidden="true" role="img" class="octicon" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" style="display:inline-block;user-select:none;vertical-align:text-bottom"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"></path></svg> <!-- -->10 min read</span></i></a><div><p>I recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here&#x27;s what I came up with…</p></div></article></main><footer width="1,1,0.824,0.4635,0.382" class="Box-sc-1b6inku-0 heZGbK Footer_footer__31Uwg"><div class="Pagehead-r7ayx1-0 dkDfm"></div><div class="Footer_footerLinks__wyMW9"><a href="/">Jerry Green</a></div></footer></div>
83<script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"data":{"procedural-world-generation":{"id":"d840242b32a2c92dacb495b3a1211b54","path":"procedural-world-generation","title":"Procedural World Generation","description":"Recently, I wanted to learn to make generators that are able to create some small seamless worlds for a game. Long time ago I would think: how on earth people do that? Here's what I came up with now...","stargazerCount":3,"commentsCount":0,"gistRepo":"https://gist.github.com/d840242b32a2c92dacb495b3a1211b54","createdAt":"2021-05-14T17:00:07Z","updatedAt":"2022-08-26T21:03:35Z","readingTime":{"text":"8 min read","minutes":7.866666666666666,"time":472000,"words":944},"mdx":"# Procedural World Generation\n\nRecently, I wanted to learn to make generators that are able to create some small seamless worlds for a game. Long time ago I would think: how on earth people do that? Here's what I came up with now...\n\n![map_static@x2](https://user-images.githubusercontent.com/13215662/116163528-5d81c380-a711-11eb-8763-ff4c61bff3f0.gif 'Procedurally generated little world')\n\n\u003c!--\nReserved\nfor Preview\n--\u003e\n\nThe generated map has some deep sea, large amount of green land with little lakes (shallow light blue water), differently sized seas here and there (deep dark blue water), some cold areas at north covered with snow and some beaches covered with sand on south. I think it's quite interesting terrain, forming one cool island 🙃\n\n## How I learned to do this\n\nPurely accidentally I found a good lecture about PCG (Procedural Content Generation) from professor [Dave Churchill](https://www.cs.mun.ca/~dchurchill/), Memorial University of Newfoundland (Canada). I found him long time ago somewhere, he's quite famous for his work on AI for custom StarCraft bots. To my surprise, he released a video lecture about this PCG topic recently, right at the moment I was interested in it! Here's his lecture:\n\n[Procedural Content Generation by Dave Churchill (1h 22m)](https://youtu.be/olj4OOw-9cs)\n\nLong story short, there are typically two ways to generate 2D maps for terrain:\n\n- [Cellular Automata](https://en.wikipedia.org/wiki/Cellular_automaton)\n- [Perlin Noise](https://en.wikipedia.org/wiki/Perlin_noise) (most popular implementation of a more general [Gradient Noise](https://en.wikipedia.org/wiki/Gradient_noise))\n\nBoth are quite interesting and very customizable, but the second one is quite harder to implement, therefore is harder to make edits. So I decided to use Cellular Automata.\n\nOf course, there are much more knowledge requirements to implement all this, like you should be quite good with programming in overall, with tooling and stuff, otherwise this single video won't be enough to do the similar. But if you're programmer already and just want to make PCG generator, then this video should be a good starter.\n\n## How it works\n\nThere are some similar yet different steps:\n\n1. Generate Sea (blank 2D map of one default value called Sea)\n2. Generate Noise (random values throughout this 2D map which make little dots on this map of Sea)\n3. Generate Water (many little steps of Cellular Automata algorithm, making dots come gather into whole areas)\n4. Generate Weed (a whole another cycle, similar to previous, but for another type of a [Tile](https://en.wikipedia.org/wiki/Tile-based_video_game), Weed)\n5. Generate Sand (yet another CA cycle, for Sand Tile)\n6. Generate Snow (another CA cycle again, for Snow Tile)\n\nHere's a gif of all the generation steps (but Sand and Snow combined into one animation step):\n\n![map@x2](https://user-images.githubusercontent.com/13215662/116163391-1c89af00-a711-11eb-8e08-670e2920eed9.gif 'Procedural world generation in action')\n\nYou can notice that both the left and right edges form a single shape, making the island pretty much seamless on X-axis. In purely naive implementation it's not seamless, but with a little tweaking it's possible to make generator generate seamless maps fairly easy. It's more clear when you duplicate the image on the sides:\n\n![map3@x2](https://user-images.githubusercontent.com/13215662/116805113-8359fe80-ab3d-11eb-8e
830d-4e573565e3c9.png 'The world is seamless')\n\nLooks almost like a surface of a miniature planet, huh?\n\n## You were interested in AssemblyScript, did you implement it in AS?\n\n\u003e TLDR: No, it's implemented in TypeScript currently.\n\nFrom my previous article, [Making Custom Game Engine](https://jerrygreen.me/blog/making-custom-game-engine), I said about my plans regarding AssemblyScript, but I've spent too many hours trying to make AssemblyScript modules work, using only the native WASM support by Node and browsers. I found out that AssemblyScript modules are little more complex than pure WASM modules, so it can't \"just work\" like normal WASM modules, by many many reasons:\n\n- [\"Module not found\" when exporting a class](https://github.com/AssemblyScript/assemblyscript/issues/1721)\n- [Default function arguments lead to unreachable runtime error](https://github.com/AssemblyScript/assemblyscript/issues/1724)\n- [Unable to use \"this\" in functions](https://github.com/AssemblyScript/assemblyscript/issues/1772)\n- etc etc\n\nInstead, you have to pass it through the [AssemblyScript Loader](https://www.assemblyscript.org/loader.html) first, to get rid of most of these errors.\n\nMy bad I started trying to make it work without the loader in the first place, but I had my reasons: it's not just additional initialization code I wouldn't like to see in a project, but I'm also unable to properly translate AssemblyScript types into TypeScript types with the loader (so anything coming from the module has `any` type).\n\nSo I gave up and moved away from AssemblyScript for a while. I'm just a normal person, and after so much frustration with it I can't just look at this AssemblyScript, its loaders, etc. I decided to write my procedural generation with just TypeScript for now.\n\n
83It is likely I'll recover from this frustration with AssemblyScript and will try it again someday, properly with the loader next time. It will also be a good test for AssemblyScript: how easy it is to rewrite existing TypeScript code into AssemblyScript, preserving all the types and functionality? We'll see.\n\n## Demo\n\nThis time I decided to make my article a bit more entertaining, so here's a tiny demo of this generator:\n\n[World Generator (demo)](https://jerrygreen.vercel.app/demo/procedural-world-generation)\n\n## What's next?\n\nI plan to write about other parts of the game I'm making time to time, - there are huge amount of different areas! And I have many ideas particularly for this generator. I'm not sure how complex the generator will be in the end, but here are some other things it might (and will likely) include:\n\n- Rocks and/or Mountains\n- Trees\n- Rivers\n\nBut for now...\n\n_That's it!_\n","contentPreview":{"compiledSource":"\"use strict\";\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i \u003c arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i \u003c sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i \u003c sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar layoutProps = {};\nvar MDXLayout = \"wrapper\";\n\nfunction MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Recently, I wanted to learn to make generators that are able to create some small seamless worlds for a game. Long time ago I would think: how on earth people do that? Here's what I came up with now...\"));\n}\n\n;\nMDXContent.isMDXComponent = true;","renderedOutput":"\u003cp\u003eRecently, I wanted to learn to make generators that are able to create some small seamless worlds for a game. Long time ago I would think: how on earth people do that? Here\u0026#x27;s what I came up with now...\u003c/p\u003e","scope":{}},"content":{"compiledSource":"\"use strict\";\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i \u003c arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i \u003c sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i \u003c sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar layoutProps = {};\nvar MDXLayout = \"wrapper\";\n\nfunction MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Recently, I wanted to learn to make generators that are able to create some small seamless worlds for a game. Long time ago I would think: how on earth people do that? Here's what I came up with 
83now...\"), mdx(\"img\", {\n    width: \"255\",\n    src: \"https://user-images.githubusercontent.com/13215662/116163528-5d81c380-a711-11eb-8763-ff4c61bff3f0.gif\",\n    alt: \"map_static\",\n    title: \"map_static\",\n    style: {\n      \"aspectRatio\": \"255 / 157\"\n    }\n  }), mdx(\"p\", null, \"Procedurally generated little world\"), mdx(\"p\", null, \"The generated map has some deep sea, large amount of green land with little lakes (shallow light blue water), differently sized seas here and there (deep dark blue water), some cold areas at north covered with snow and some beaches covered with sand on south. I think it's quite interesting terrain, forming one cool island \\uD83D\\uDE43\"), mdx(\"h2\", null, \"How I learned to do this\"), mdx(\"p\", null, \"Purely accidentally I found a good lecture about PCG (Procedural Content Generation) from professor \", mdx(\"a\", {\n    href: \"https://www.cs.mun.ca/~dchurchill/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Dave Churchill\"), \", Memorial University of Newfoundland (Canada). I found him long time ago somewhere, he's quite famous for his work on AI for custom StarCraft bots. To my surprise, he released a video lecture about this PCG topic recently, right at the moment I was interested in it! Here's his lecture:\"), mdx(\"iframe\", {\n    width: \"853\",\n    src: \"https://www.youtube.com/embed/olj4OOw-9cs?start=\u0026end=\u0026modestbranding=1\u0026rel=0\",\n    frameBorder: \"0\",\n    allow: \"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\",\n    allowFullScreen: true,\n    style: {\n      \"aspectRatio\": \"1080 / 607.5\"\n    }\n  }), mdx(\"p\", null, \"Procedural Content Generation by Dave Churchill (1h 22m)\"), mdx(\"p\", null, \"Long story short, there are typically two ways to generate 2D maps for terrain:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://en.wikipedia.org/wiki/Cellular_automaton\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Cellular Automata\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://en.wikipedia.org/wiki/Perlin_noise\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Perlin Noise\"), \" (most popular implementation of a more general \", mdx(\"a\", {\n    href: \"https://en.wikipedia.org/wiki/Gradient_noise\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Gradient Noise\"), \")\")), mdx(\"p\", null, \"Both are quite interesting and very customizable, but the second one is quite harder to implement, therefore is harder to make edits. So I decided to use Cellular Automata.\"), mdx(\"p\", null, \"Of course, there are much more knowledge requirements to implement all this, like you should be quite good with programming in overall, with tooling and stuff, otherwise this single video won't be enough to do the similar. But if you're programmer already and just want to make PCG generator, then this video should be a good starter.\"), mdx(\"h2\", null, \"How it works\"), mdx(\"p\", null, \"There are some similar yet different steps:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Generate Sea (blank 2D map of one default value called Sea)\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Generate Noise (random values throughout this 2D map which make little dots on this map of Sea)\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Generate Water (many little steps of Cellular Automata algorithm, making dots come gather into whole areas)\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Generate Weed (a whole another cycle, similar to previous, but for another type of a \", mdx(\"a\", {\n    href: \"https://en.wikipedia.org/wiki/Tile-based_video_game\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Tile\"), \", Weed)\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Generate Sand (yet another CA cycle, for Sand Tile)\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Generate Snow (another CA cycle again, for Snow Tile)\")), mdx(\"p\", null, \"Here's a gif of all the generation steps (but Sand and Snow combined into one animation step):\"), mdx(\"img\", {\n    width: \"255\",\n    src: \"https://user-images.githubusercontent.com/13215662/116163391-1c89af00-a711-11eb-8e
8308-670e2920eed9.gif\",\n    alt: \"map\",\n    title: \"map\",\n    style: {\n      \"aspectRatio\": \"255 / 157\"\n    }\n  }), mdx(\"p\", null, \"Procedural world generation in action\"), mdx(\"p\", null, \"You can notice that both the left and right edges form a single shape, making the island pretty much seamless on X-axis. In purely naive implementation it's not seamless, but with a little tweaking it's possible to make generator generate seamless maps fairly easy. It's more clear when you duplicate the image on the sides:\"), mdx(\"img\", {\n    width: \"768\",\n    src: \"https://user-images.githubusercontent.com/13215662/116805113-8359fe80-ab3d-11eb-8e0d-4e573565e3c9.png\",\n    alt: \"map3\",\n    title: \"map3\",\n    style: {\n      \"aspectRatio\": \"768 / 158\"\n    }\n  }), mdx(\"p\", null, \"The world is seamless\"), mdx(\"p\", null, \"Looks almost like a surface of a miniature planet, huh?\"), mdx(\"h2\", null, \"You were interested in AssemblyScript, did you implement it in AS?\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"TLDR: No, it's implemented in TypeScript currently.\")), mdx(\"p\", null, \"From my previous article, \", mdx(\"a\", {\n    href: \"/blog/making-custom-game-engine\",\n    rel: \"noopener\"\n  }, \"Making Custom Game Engine\"), \", I said about my plans regarding AssemblyScript, but I've spent too many hours trying to make AssemblyScript modules work, using only the native WASM support by Node and browsers. I found out that AssemblyScript modules are little more complex than pure WASM modules, so it can't \\\"just work\\\" like normal WASM modules, by many many reasons:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://github.com/AssemblyScript/assemblyscript/issues/1721\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"\\\"Module not found\\\" when exporting a class\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://github.com/AssemblyScript/assemblyscript/issues/1724\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Default function arguments lead to unreachable runtime error\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://github.com/AssemblyScript/assemblyscript/issues/1772\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Unable to use \\\"this\\\" in functions\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"etc etc\")), mdx(\"p\", null, \"Instead, you have to pass it through the \", mdx(\"a\", {\n    href: \"https://www.assemblyscript.org/loader.html\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"AssemblyScript Loader\"), \" first, to get rid of most of these errors.\"), mdx(\"p\", null, \"My bad I started trying to make it work without the loader in the first place, but I had my reasons: it's not just additional initialization code I wouldn't like to see in a project, but I'm also unable to properly translate AssemblyScript types into TypeScript types with the loader (so anything coming from the module has \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"any\"), \" type).\"), mdx(\"p\", null, \"So I gave up and moved away from AssemblyScript for a while. I'm just a normal person, and after so much frustration with it I can't just look at this AssemblyScript, its loaders, etc. I decided to write my procedural generation with just TypeScript for now.\"), mdx(\"p\", null, \"It is likely I'll recover from this frustration with AssemblyScript and will try it again someday, properly with the loader next time. It will also be a good test for AssemblyScript: how easy it is to rewrite existing TypeScript code into AssemblyScript, preserving all the types and functionality? We'll see.\"), mdx(\"h2\", null, \"Demo\"), mdx(\"p\", null, \"This time I decided to make my article a bit more entertaining, so here's a tiny demo of this generator:\"), mdx(\"demo\", null, mdx(\"a\", {\n    href: \"/demo/procedural-world-generation\",\n    target: \"_blank\"\n  }), mdx(\"div\", {\n    className: \"demo_wrapper\"\n  }, mdx(ProceduralWorldGeneration, {\n    mdxType: \"ProceduralWorldGeneration\"\n  }))), mdx(\"p\", null, \"World Generator (demo)\"), mdx(\"h2\", null, \"What's next?\"), mdx(\"p\", null, \"I plan to write about other parts of the game I'm making time to time, - there are huge amount of different areas! And I have many ideas particularly for this generator. I'm not sure how complex the generator will be in the end, but here are some other things it might (and will likely) include:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Rocks and/or Mountains\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Trees\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Rivers\")), mdx(\"p\", null, \"But for now...\"), mdx(\"p\", null, mdx(\"em\", {\n    parentName: \"p\"\n  }, \"That's it!\")));\n}\n\n;\nMDXContent.isMDXComponent = true;","renderedOutput":"\u003cp\u003eRecently, I wanted to learn to make generators that are able to create some small seamless worlds for a game. Long time ago I would think: how on earth people do that? Here\u0026#x27;
83s what I came up with now...\u003c/p\u003e\u003cimg width=\"255\" src=\"https://user-images.githubusercontent.com/13215662/116163528-5d81c380-a711-11eb-8763-ff4c61bff3f0.gif\" alt=\"map_static\" title=\"map_static\" style=\"aspect-ratio:255 / 157\"/\u003e\u003cp\u003eProcedurally generated little world\u003c/p\u003e\u003cp\u003eThe generated map has some deep sea, large amount of green land with little lakes (shallow light blue water), differently sized seas here and there (deep dark blue water), some cold areas at north covered with snow and some beaches covered with sand on south. I think it\u0026#x27;s quite interesting terrain, forming one cool island 🙃\u003c/p\u003e\u003ch2\u003eHow I learned to do this\u003c/h2\u003e\u003cp\u003ePurely accidentally I found a good lecture about PCG (Procedural Content Generation) from professor \u003ca href=\"https://www.cs.mun.ca/~dchurchill/\" rel=\"noopener\" target=\"_blank\"\u003eDave Churchill\u003c/a\u003e, Memorial University of Newfoundland (Canada). I found him long time ago somewhere, he\u0026#x27;s quite famous for his work on AI for custom StarCraft bots. To my surprise, he released a video lecture about this PCG topic recently, right at the moment I was interested in it! Here\u0026#x27;s his lecture:\u003c/p\u003e\u003ciframe width=\"853\" src=\"https://www.youtube.com/embed/olj4OOw-9cs?start=\u0026amp;end=\u0026amp;modestbranding=1\u0026amp;rel=0\" frameBorder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen=\"\" style=\"aspect-ratio:1080 / 607.5\"\u003e\u003c/iframe\u003e\u003cp\u003eProcedural Content Generation by Dave Churchill (1h 22m)\u003c/p\u003e\u003cp\u003eLong story short, there are typically two ways to generate 2D maps for terrain:\u003c/p\u003e\u003cul\u003e\u003cli\u003e\u003ca href=\"https://en.wikipedia.org/wiki/Cellular_automaton\" rel=\"noopener\" target=\"_blank\"\u003eCellular Automata\u003c/a\u003e\u003c/li\u003e\u003cli\u003e\u003ca href=\"https://en.wikipedia.org/wiki/Perlin_noise\" rel=\"noopener\" target=\"_blank\"\u003ePerlin Noise\u003c/a\u003e (most popular implementation of a more general \u003ca href=\"https://en.wikipedia.org/wiki/Gradient_noise\" rel=\"noopener\" target=\"_blank\"\u003eGradient Noise\u003c/a\u003e)\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eBoth are quite interesting and very customizable, but the second one is quite harder to implement, therefore is harder to make edits. So I decided to use Cellular Automata.\u003c/p\u003e\u003cp\u003eOf course, there are much more knowledge requirements to implement all this, like you should be quite good with programming in overall, with tooling and stuff, otherwise this single video won\u0026#x27;t be enough to do the similar. But if you\u0026#x27;re programmer already and just want to make PCG generator, then this video should be a good starter.\u003c/p\u003e\u003ch2\u003eHow it works\u003c/h2\u003e\u003cp\u003eThere are some similar yet different steps:\u003c/p\u003e\u003col\u003e\u003cli\u003eGenerate Sea (blank 2D map of one default value called Sea)\u003c/li\u003e\u003cli\u003eGenerate Noise (random values throughout this 2D map which make little dots on this map of Sea)\u003c/li\u003e\u003cli\u003eGenerate Water (many little steps of Cellular Automata algorithm, making dots come gather into whole areas)\u003c/li\u003e\u003cli\u003eGenerate Weed (a whole another cycle, similar to previous, but for another type of a \u003ca href=\"https://en.wikipedia.org/wiki/Tile-based_video_game\" rel=\"noopener\" target=\"_blank\"\u003eTile\u003c/a\u003e, Weed)\u003c/li\u003e\u003cli\u003eGenerate Sand (yet another CA cycle, for Sand Tile)\u003c/li\u003e\u003cli\u003eGenerate Snow (another CA cycle again, for Snow Tile)\u003c/li\u003e\u003c/ol\u003e\u003cp\u003eHere\u0026#x27;s a gif of all the generation steps (but Sand and Snow combined into one animation step):\u003c/p\u003e\u003cimg width=\"255\" src=\"https://user-images.githubusercontent.com/13215662/116163391-1c89af00-a711-11eb-8e08-670e2920eed9.gif\" alt=\"map\" title=\"map\" style=\"aspect-ratio:255 / 157\"/\u003e\u003cp\u003eProcedural world generation in action\u003c/p\u003e\u003cp\u003eYou can notice that both the left and right edges form a single shape, making the island pretty much seamless on X-axis. In purely naive implementation it\u0026#x27;s not seamless, but with a little tweaking it\u0026#x27;s possible to make generator generate seamless maps fairly easy. It\u0026#x27;s more clear when you duplicate the image on the sides:\u003c/p\u003e\u003cimg width=\"768\" src=\"https://user-images.githubusercontent.com/13215662/116805113-8359fe80-ab3d-11eb-8e
830d-4e573565e3c9.png\" alt=\"map3\" title=\"map3\" style=\"aspect-ratio:768 / 158\"/\u003e\u003cp\u003eThe world is seamless\u003c/p\u003e\u003cp\u003eLooks almost like a surface of a miniature planet, huh?\u003c/p\u003e\u003ch2\u003eYou were interested in AssemblyScript, did you implement it in AS?\u003c/h2\u003e\u003cblockquote\u003e\u003cp\u003eTLDR: No, it\u0026#x27;s implemented in TypeScript currently.\u003c/p\u003e\u003c/blockquote\u003e\u003cp\u003eFrom my previous article, \u003ca href=\"/blog/making-custom-game-engine\" rel=\"noopener\"\u003eMaking Custom Game Engine\u003c/a\u003e, I said about my plans regarding AssemblyScript, but I\u0026#x27;ve spent too many hours trying to make AssemblyScript modules work, using only the native WASM support by Node and browsers. I found out that AssemblyScript modules are little more complex than pure WASM modules, so it can\u0026#x27;t \u0026quot;just work\u0026quot; like normal WASM modules, by many many reasons:\u003c/p\u003e\u003cul\u003e\u003cli\u003e\u003ca href=\"https://github.com/AssemblyScript/assemblyscript/issues/1721\" rel=\"noopener\" target=\"_blank\"\u003e\u0026quot;Module not found\u0026quot; when exporting a class\u003c/a\u003e\u003c/li\u003e\u003cli\u003e\u003ca href=\"https://github.com/AssemblyScript/assemblyscript/issues/1724\" rel=\"noopener\" target=\"_blank\"\u003eDefault function arguments lead to unreachable runtime error\u003c/a\u003e\u003c/li\u003e\u003cli\u003e\u003ca href=\"https://github.com/AssemblyScript/assemblyscript/issues/1772\" rel=\"noopener\" target=\"_blank\"\u003eUnable to use \u0026quot;this\u0026quot; in functions\u003c/a\u003e\u003c/li\u003e\u003cli\u003eetc etc\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eInstead, you have to pass it through the \u003ca href=\"https://www.assemblyscript.org/loader.html\" rel=\"noopener\" target=\"_blank\"\u003eAssemblyScript Loader\u003c/a\u003e first, to get rid of most of these errors.\u003c/p\u003e\u003cp\u003eMy bad I started trying to make it work without the loader in the first place, but I had my reasons: it\u0026#x27;s not just additional initialization code I wouldn\u0026#x27;t like to see in a project, but I\u0026#x27;m also unable to properly translate AssemblyScript types into TypeScript types with the loader (so anything coming from the module has \u003ccode\u003eany\u003c/code\u003e type).\u003c/p\u003e\u003cp\u003eSo I gave up and moved away from AssemblyScript for a while. I\u0026#x27;m just a normal person, and after so much frustration with it I can\u0026#x27;t just look at this AssemblyScript, its loaders, etc. I decided to write my procedural generation with just TypeScript for now.\u003c/p\u003e\u003cp\u003eIt is likely I\u0026#x27;ll recover from this frustration with AssemblyScript and will try it again someday, properly with the loader next time. It will also be a good test for AssemblyScript: how easy it is to rewrite existing TypeScript code into AssemblyScript, preserving all the types and functionality? We\u0026#x27;ll see.\u003c/p\u003e\u003ch2\u003eDemo\u003c/h2\u003e\u003cp\u003eThis time I decided to make my article a bit more entertaining, so here\u0026#x27;s a tiny demo of this generator:\u003c/p\u003e\u003cdemo\u003e\u003ca href=\"/demo/procedural-world-generation\" target=\"_blank\"\u003e\u003c/a\u003e\u003cdiv class=\"demo_wrapper\"\u003e\u003cdiv\u003e\u003c/div\u003e\u003c/div\u003e\u003c/demo\u003e\u003cp\u003eWorld Generator (demo)\u003c/p\u003e\u003ch2\u003eWhat\u0026#x27;s next?\u003c/h2\u003e\u003cp\u003eI plan to write about other parts of the game I\u0026#x27;m making time to time, - there are huge amount of different areas! And I have many ideas particularly for this generator. I\u0026#x27;m not sure how complex the generator will be in the end, but here are some other things it might (and will likely) include:\u003c/p\u003e\u003cul\u003e\u003cli\u003eRocks and/or Mountains\u003c/li\u003e\u003cli\u003eTrees\u003c/li\u003e\u003cli\u003eRivers\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eBut for now...\u003c/p\u003e\u003cp\u003e\u003cem\u003eThat\u0026#x27;s it!\u003c/em\u003e\u003c/p\u003e","scope":{}},"imagePreview":{"src":"https://user-images.githubusercontent.com/13215662/116163528-5d81c38
830-a711-11eb-8763-ff4c61bff3f0.gif","alt":"map_static","title":"Procedurally generated little world"}},"making-custom-game-engine":{"id":"b13ffe723184dba73c6e044e9ae8f543","path":"making-custom-game-engine","title":"Making Custom Game Engine","description":"In this article I'll cover some in-depth benchmarks of what I built and play around it a bit.","stargazerCount":3,"commentsCount":8,"gistRepo":"https://gist.github.com/b13ffe723184dba73c6e044e9ae8f543","createdAt":"2021-03-27T00:22:50Z","updatedAt":"2022-03-25T16:02:51Z","readingTime":{"text":"11 min read","minutes":10.75,"time":645000,"words":1290},"mdx":"# Making Custom Game Engine\n\n\u003e In my [previous article](https://jerrygreen.vercel.app/blog/beginning-game-development) I covered what's ECS in overall, and what's ECSY, some other tools and potential bottlenecks.\n\nIn this article I'll cover some in-depth benchmarks of what I built and play around it a bit.\n\n\u003c!--\nReserved\nfor preview\n--\u003e\n\nBehind the scenes, before writing this article, I have quickly rewritten `.js` code examples to `.ts` and got rid of `ECSY` library:\n\n![languages@x2](https://user-images.githubusercontent.com/13215662/112785062-9116f280-906c-11eb-9f1f-2b6cc18ce9ab.png '90% of code is TypeScript now')\n\n`ECSY` has neat concept but I figured out I don't like its internals and replaced it with my own ECS implementation. And even though my goal was just to get everything sorted and look good, clean, and typed, - it allowed me to achieve like at least x20 less GPU usage:\n\n![frames@x2](https://user-images.githubusercontent.com/13215662/112785064-93794c80-906c-11eb-917a-3e8b175a8139.png 'x20 less GPU usage now')\n\nI initially got bombuzled because I thought it's all on my CPU now, and I broke all the performance. Of course you can't notice the difference on such a simple example without hacky tricks: it runs smoothly at full 60fps in both cases. But with x6 throttling on CPU, I quickly realized I now have better CPU usage, too:\n\n![6x-throttle@x2](https://user-images.githubusercontent.com/13215662/112785066-95431000-906c-11eb-96cd-32883be63757.png 'x6 throttled Intel i7-9700, before and after: now it has less frame drops')\n\nSo it turns out clean code and types lead to performant applications and games. Who knew?\n\nNow the project structure:\n\n```bash\n. # root folder with all the config files\nsrc # folder with source code\nsrc/asm # experimental folder with AssemblyScript files that are later compiled into `.wasm` modules\nsrc/components # the first whale of ECS pattern: components\nsrc/elements # `.tsx` React Elements to render UI like buttons and menus\nsrc/entities # second whale of ECS pattern: entities\nsrc/game # the rest files related to game logic, like World.ts\nsrc/helpers # little utility functions\nsrc/main # electron \"main process\"-related files like background.ts and create-window.ts\nsrc/pages # all possible screens that I'll have in game, at this step is not actively used though\nsrc/systems # finally third whale of ECS pattern: systems\n```\n\nSince components are basically javascript objects for data storage, my whole ECS implementation ended up with those three files:\n\n```bash\nsrc/game/Entity.ts # Base class for all entities to inherit\nsrc/game/System.ts # Base class for all systems to inherit\nsrc/game/World.ts # Almost like singleton-class, no inheritance needed\n```\n\nAnd instead of so-called ECS \"Queries\", well... Have you heard about such a thing as objects? Hashes? Associative arrays, anyone? Why everything should be stored in classes? So I got rid of such class as \"Component\" and consecutively I no more need those weird \"Queries\". Also, I don't store anything in another consuming data structure called \"Collections\" (basically arrays of objects). Collections are overrated and simply slow. So I use only plain arrays and nested objects, never an object inside an array (except for input arguments but never for storing). Pretty much like React, actually... This way I get beautiful syntax for accessing data out of the box.\n\nSo instead of this awful API to extract components:\n\n```ts\nentity.getComponent(Acceleration).acceleration\nentity.getComponent(Position).position\n// etc...\n```\n\nI can extract components much simpler now:\n\n```ts\nentity.props.velocity\nentity.props.position\n// etc...\n```\n\nAnd the best part is that they're perfectly typed, so unlike in ECSY, I now have hints for the components and their data:\n\n![types@x2](https://user-images.githubusercontent.com/13215662/110256402-8e1f6980-7fba-11eb-858f-18a087cd1096.PNG 'Now I have those precious hints')\n\nThose hints are based on each particular System, so it can only access those properties related to a system:\n\n```ts\nconst Renderable = { ...Position, ...Shape } // \u003c-- I can only access those components, related to the system\n\nexport default class RenderableSystem extends System\u003ctypeof Renderable\u003e { ... }\n```\n\nSo it turns out I don't need any special structures for querying data: JSON is my API. I guess this is one of the main reasons why my implementation got more lightweight and performant than ECSY framework.\n\nBasically it's all just a web application on steroids, so in my root React component I use a couple React Hooks to do the following.\n\n```ts\nconst world = useMemo(() =\u003e {\n  if (ctx) {\n    const wd = new World({\n      systems: [\n        /* World initialized with these listed systems */\n        /* It is able to fill them up dynamically later */\n      ],\n      entities: [\n        /* Might initialize world 
83with some entities */\n        /* Also able to add them dynamically later */\n      ],\n    })\n    return wd\n  }\n}, [ctx])\n```\n\nAnd this is how game loop gets started, when world is initialized:\n\n```ts\nuseEffect(() =\u003e {\n  if (world) {\n    console.info('Game loaded!')\n    world.run(performance.now(), setFps)\n  }\n}, [world])\n```\n\nAt this point we're basically out of React paradigm, and can make all the logic inside our ECS paradigm.\n\nI can still use React to render whatever UI on top of the game, accessing systems, entities, their components and data. Which is kinda cool: React for user interface menus and buttons, ECS for game loop and continuous game logic handling.\n\n## Is ECSY dead?\n\nI heard [this question on GitHub](https://github.com/ecsyjs/ecsy/issues/279), and the answer tends to be \"yes\", - in addition to all the performance problems and awful syntax it involves, it seems to be unmaintained too.\n\nI initially liked ECSY but quickly realized I like the ECS pattern, not the ECSY library.\n\nSome people still try to adopt it by making things like [ecstra](https://github.com/DavidPeicho/ecstra), though I'm sceptic about it, since it's still based on ECSY with all its weird queries and component-classes.\n\nEven with custom ECS implementation, I still face the same problem I mentioned in my [previous article](https://jerrygreen.me/blog/beginning-game-development) though: `RenderableSystem`. It's very naive and still re-draws the whole screen entirely as before, with no optimizations, so it's still the bottleneck of whatever engine I'm coming up with, and still upon my attentive observation. First candidate for replacement.\n\n## What's about AssemblyScript?\n\nI currently still have no use to that basically, but I have plans on it, and I'm preparing some ground for it. I made a couple Pull Requests so it can be actually usable to me, with all the conveniences I'm used to. Like TypeScript hints: [AssemblyScript#1705](https://github.com/AssemblyScript/assemblyscript/pull/1705), and globs: [AssemblyScript#1716](https://github.com/AssemblyScript/assemblyscript/pull/1716). I'm using previously mentioned `1 + 2 = 3` little WASM module, just to make sure it's all working, with my own fork listed in my `package.json`, before my changes get landed into AssemblyScript:\n\n```json package\n{\n  \"devDependencies\": {\n    \"assemblyscript\": \"https://github.com/jerrygreen/assemblyscript/tarball/jerrygreen\"\n  }\n}\n```\n\n## Conclusion\n\nECS is a neat architecture, but ECSY framework is poorly designed, non-performant, and basically unmaintained, so I came up with writing my own ECS implementation that fits my needs, fortunately the concept is real simple.\n\nEven if I trust the rendering part to some library rather than my custom `RenderableSystem` class, I might still use the rest of my ECS implementation for calculating game logic, and even move it to server-side, to orchestrate all the world interactions for multiplayer, for example. Fortunately, no any `RenderableSystem` needed on server, so basically no such bottleneck at all.\n\nI don't have much functionality in my game yet: still just a few flying circles and rectangles. I'm hoping to feature other interesting parts of the project I'm making. But for now...\n\n_That's it!_\n","contentPreview":{"compiledSource":"\"use strict\";\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i \u003c arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i \u003c sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i \u003c sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar layoutProps = {};\nvar MDXLayout = \"wrapper\";\n\nfunction MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"In this article I'll cover some in-depth benchmarks of what I built and play around it a bit.\"));\n}\n\n;\nMDXContent.isMDXComponent = true;","renderedOutput":"\u003cp\u003eIn this article I\u0026#x27;ll cover some in-depth benchmarks of what I built and play around it a bit.\u003c/p\u003e","scope":{}},"content":{"compiledSource":"\"use strict\";\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i \u003c arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i \u003c sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i \u003c sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar layoutProps = {};\nvar MDXLayout = \"wrapper\";\n\nfunction MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"In my \", mdx(\"a\", {\n    href: \"/blog/beginning-game-development\",\n    rel: \"noopener\"\n  }, \"previous article\"), \" I covered what's ECS in overall, and what's ECSY, some other tools and potential bottlenecks.\")), mdx(\"p\", null, \"In this article I'll cover some in-depth benchmarks of what I built and play around it a bit.\"), mdx(\"p\", null, \"Behind the scenes, before writing this article, I have quickly rewritten \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".js\"), \" code examples to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".ts\"), \" and got rid of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ECSY\"), \" library:\"), mdx(\"img\", {\n    width: \"700\",\n    src: \"https://user-images.githubusercontent.com/13215662/112785062-9116f280-906c-11eb-9f1f-2b6cc18ce9ab.png\",\n    alt: \"languages\",\n    title: \"languages\",\n    style: {\
83n      \"aspectRatio\": \"700 / 148\"\n    }\n  }), mdx(\"p\", null, \"90% of code is TypeScript now\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ECSY\"), \" has neat concept but I figured out I don't like its internals and replaced it with my own ECS implementation. And even though my goal was just to get everything sorted and look good, clean, and typed, - it allowed me to achieve like at least x20 less GPU usage:\"), mdx(\"img\", {\n    width: \"490\",\n    src: \"https://user-images.githubusercontent.com/13215662/112785064-93794c80-906c-11eb-917a-3e8b175a8139.png\",\n    alt: \"frames\",\n    title: \"frames\",\n    style: {\n      \"aspectRatio\": \"490 / 185\"\n    }\n  }), mdx(\"p\", null, \"x20 less GPU usage now\"), mdx(\"p\", null, \"I initially got bombuzled because I thought it's all on my CPU now, and I broke all the performance. Of course you can't notice the difference on such a simple example without hacky tricks: it runs smoothly at full 60fps in both cases. But with x6 throttling on CPU, I quickly realized I now have better CPU usage, too:\"), mdx(\"img\", {\n    width: \"490\",\n    src: \"https://user-images.githubusercontent.com/13215662/112785066-95431000-906c-11eb-96cd-32883be63757.png\",\n    alt: \"6x-throttle\",\n    title: \"6x-throttle\",\n    style: {\n      \"aspectRatio\": \"490 / 185\"\n    }\n  }), mdx(\"p\", null, \"x6 throttled Intel i7-9700, before and after: now it has less frame drops\"), mdx(\"p\", null, \"So it turns out clean code and types lead to performant applications and games. Who knew?\"), mdx(\"p\", null, \"Now the project structure:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-bash\",\n    dangerouslySetInnerHTML: {\n      __html: \". \u003cspan class=\\\"hljs-comment\\\"\u003e# root folder with all the config files\u003c/span\u003e\\nsrc \u003cspan class=\\\"hljs-comment\\\"\u003e# folder with source code\u003c/span\u003e\\nsrc/asm \u003cspan class=\\\"hljs-comment\\\"\u003e# experimental folder with AssemblyScript files that are later compiled into `.wasm` modules\u003c/span\u003e\\nsrc/components \u003cspan class=\\\"hljs-comment\\\"\u003e# the first whale of ECS pattern: components\u003c/span\u003e\\nsrc/elements \u003cspan class=\\\"hljs-comment\\\"\u003e# `.tsx` React Elements to render UI like buttons and menus\u003c/span\u003e\\nsrc/entities \u003cspan class=\\\"hljs-comment\\\"\u003e# second whale of ECS pattern: entities\u003c/span\u003e\\nsrc/game \u003cspan class=\\\"hljs-comment\\\"\u003e# the rest files related to game logic, like World.ts\u003c/span\u003e\\nsrc/helpers \u003cspan class=\\\"hljs-comment\\\"\u003e# little utility functions\u003c/span\u003e\\nsrc/main \u003cspan class=\\\"hljs-comment\\\"\u003e# electron \u0026quot;main process\u0026quot;-related files like background.ts and create-window.ts\u003c/span\u003e\\nsrc/pages \u003cspan class=\\\"hljs-comment\\\"\u003e# all possible screens that I\u0026#x27;ll have in game, at this step is not actively used though\u003c/span\u003e\\nsrc/systems \u003cspan class=\\\"hljs-comment\\\"\u003e# finally third whale of ECS pattern: systems\u003c/span\u003e\"\n    }\n  })), mdx(\"p\", null, \"Since components are basically javascript objects for data storage, my whole ECS implementation ended up with those three files:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-bash\",\n    dangerouslySetInnerHTML: {\n      __html: \"src/game/Entity.ts \u003cspan class=\\\"hljs-comment\\\"\u003e# Base class for all entities to inherit\u003c/span\u003e\\nsrc/game/System.ts \u003cspan class=\\\"hljs-comment\\\"\u003e# Base class for all systems to inherit\u003c/span\u003e\\nsrc/game/World.ts \u003cspan class=\\\"hljs-comment\\\"\u003e# Almost like singleton-class, no inheritance needed\u003c/span\u003e\"\n    }\n  })), mdx(\"p\", null, \"And instead of so-called ECS \\\"Queries\\\", well... Have you heard about such a thing as objects? Hashes? Associative arrays, anyone? Why everything should be stored in classes? So I got rid of such class as \\\"Component\\\" and consecutively I no more need those weird \\\"Quer
83ies\\\". Also, I don't store anything in another consuming data structure called \\\"Collections\\\" (basically arrays of objects). Collections are overrated and simply slow. So I use only plain arrays and nested objects, never an object inside an array (except for input arguments but never for storing). Pretty much like React, actually... This way I get beautiful syntax for accessing data out of the box.\"), mdx(\"p\", null, \"So instead of this awful API to extract components:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-ts\",\n    dangerouslySetInnerHTML: {\n      __html: \"entity.getComponent(Acceleration).acceleration\\nentity.getComponent(Position).position\\n\u003cspan class=\\\"hljs-comment\\\"\u003e// etc...\u003c/span\u003e\"\n    }\n  })), mdx(\"p\", null, \"I can extract components much simpler now:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-ts\",\n    dangerouslySetInnerHTML: {\n      __html: \"entity.props.velocity\\nentity.props.position\\n\u003cspan class=\\\"hljs-comment\\\"\u003e// etc...\u003c/span\u003e\"\n    }\n  })), mdx(\"p\", null, \"And the best part is that they're perfectly typed, so unlike in ECSY, I now have hints for the components and their data:\"), mdx(\"img\", {\n    width: \"630\",\n    src: \"https://user-images.githubusercontent.com/13215662/110256402-8e1f6980-7fba-11eb-858f-18a087cd1096.PNG\",\n    alt: \"types\",\n    title: \"types\",\n    style: {\n      \"aspectRatio\": \"630 / 121\"\n    }\n  }), mdx(\"p\", null, \"Now I have those precious hints\"), mdx(\"p\", null, \"Those hints are based on each particular System, so it can only access those properties related to a system:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-ts\",\n    dangerouslySetInnerHTML: {\n      __html: \"\u003cspan class=\\\"hljs-keyword\\\"\u003econst\u003c/span\u003e Renderable = { ...Position, ...Shape } \u003cspan class=\\\"hljs-comment\\\"\u003e// \u0026lt;-- I can only access those components, related to the system\u003c/span\u003e\\n\\n\u003cspan class=\\\"hljs-keyword\\\"\u003eexport\u003c/span\u003e \u003cspan class=\\\"hljs-keyword\\\"\u003edefault\u003c/span\u003e \u003cspan class=\\\"hljs-class\\\"\u003e\u003cspan class=\\\"hljs-keyword\\\"\u003eclass\u003c/span\u003e \u003cspan class=\\\"hljs-title\\\"\u003eRenderableSystem\u003c/span\u003e \u003cspan class=\\\"hljs-keyword\\\"\u003eextends\u003c/span\u003e \u003cspan class=\\\"hljs-title\\\"\u003eSystem\u003c/span\u003e\u0026lt;\u003cspan class=\\\"hljs-title\\\"\u003etypeof\u003c/span\u003e \u003cspan class=\\\"hljs-title\\\"\u003eRenderable\u003c/span\u003e\u0026gt; \u003c/span\u003e{ ... }\"\n    }\n  })), mdx(\"p\", null, \"So it turns out I don't need any special structures for querying data: JSON is my API. I guess this is one of the main reasons why my implementation got more lightweight and performant than ECSY framework.\"), mdx(\"p\", null, \"Basically it's all just a web application on steroids, so in my root React component I use a couple React Hooks to do the following.\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-ts\",\n    dangerouslySetInnerHTML: {\n      __html: \"\u003cspan class=\\\"hljs-keyword\\\"\u003econst\u003c/span\u003e world = useMemo(\u003cspan class=\\\"hljs-function\\\"\u003e() =\u0026gt;\u003c/span\u003e {\\n  \u003cspan class=\\\"hljs-keyword\\\"\u003eif\u003c/span\u003e (ctx) {\\n    \u003cspan class=\\\"hljs-keyword\\\"\u003econst\u003c/span\u003e wd = \u003cspan class=\\\"hljs-keyword\\\"\u003enew\u003c/span\u003e World({\\n      \u003cspan class=\\\"hljs-attr\\\"\u003esystems\u003c/span\u003e: [\\n        \u003cspan class=\\\"hljs-comment\\\"\u003e/* World initialized with these listed systems */\u003c/span\u003e\\n        \u003cspan class=\\\"hljs-comment\\\"\u003e/* It is able to fill them up dynamically later */\u003c/span\u003e\\n      ],\\n      \u003cspan class=\\\"hljs-attr\\\"\u003eentities\u003c/span\u003e: [\\n        \u003cspan class=\\\"hljs-comment\\\"\u003e/* Might initialize world 
83with some entities */\u003c/span\u003e\\n        \u003cspan class=\\\"hljs-comment\\\"\u003e/* Also able to add them dynamically later */\u003c/span\u003e\\n      ],\\n    })\\n    \u003cspan class=\\\"hljs-keyword\\\"\u003ereturn\u003c/span\u003e wd\\n  }\\n}, [ctx])\"\n    }\n  })), mdx(\"p\", null, \"And this is how game loop gets started, when world is initialized:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-ts\",\n    dangerouslySetInnerHTML: {\n      __html: \"useEffect(\u003cspan class=\\\"hljs-function\\\"\u003e() =\u0026gt;\u003c/span\u003e {\\n  \u003cspan class=\\\"hljs-keyword\\\"\u003eif\u003c/span\u003e (world) {\\n    \u003cspan class=\\\"hljs-built_in\\\"\u003econsole\u003c/span\u003e.info(\u003cspan class=\\\"hljs-string\\\"\u003e\u0026#x27;Game loaded!\u0026#x27;\u003c/span\u003e)\\n    world.run(performance.now(), setFps)\\n  }\\n}, [world])\"\n    }\n  })), mdx(\"p\", null, \"At this point we're basically out of React paradigm, and can make all the logic inside our ECS paradigm.\"), mdx(\"p\", null, \"I can still use React to render whatever UI on top of the game, accessing systems, entities, their components and data. Which is kinda cool: React for user interface menus and buttons, ECS for game loop and continuous game logic handling.\"), mdx(\"h2\", null, \"Is ECSY dead?\"), mdx(\"p\", null, \"I heard \", mdx(\"a\", {\n    href: \"https://github.com/ecsyjs/ecsy/issues/279\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"this question on GitHub\"), \", and the answer tends to be \\\"yes\\\", - in addition to all the performance problems and awful syntax it involves, it seems to be unmaintained too.\"), mdx(\"p\", null, \"I initially liked ECSY but quickly realized I like the ECS pattern, not the ECSY library.\"), mdx(\"p\", null, \"Some people still try to adopt it by making things like \", mdx(\"a\", {\n    href: \"https://github.com/DavidPeicho/ecstra\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"ecstra\"), \", though I'm sceptic about it, since it's still based on ECSY with all its weird queries and component-classes.\"), mdx(\"p\", null, \"Even with custom ECS implementation, I still face the same problem I mentioned in my \", mdx(\"a\", {\n    href: \"/blog/beginning-game-development\",\n    rel: \"noopener\"\n  }, \"previous article\"), \" though: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RenderableSystem\"), \". It's very naive and still re-draws the whole screen entirely as before, with no optimizations, so it's still the bottleneck of whatever engine I'm coming up with, and still upon my attentive observation. First candidate for replacement.\"), mdx(\"h2\", null, \"What's about AssemblyScript?\"), mdx(\"p\", null, \"I currently still have no use to that basically, but I have plans on it, and I'm preparing some ground for it. I made a couple Pull Requests so it can be actually usable to me, with all the conveniences I'm used to. Like TypeScript hints: \", mdx(\"a\", {\n    href: \"https://github.com/AssemblyScript/assemblyscript/pull/1705\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"AssemblyScript#1705\"), \", and globs: \", mdx(\"a\", {\n    href: \"https://github.com/AssemblyScript/assemblyscript/pull/1716\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"AssemblyScript#1716\"), \". I'm using previously mentioned \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"1 + 2 = 3\"), \" little WASM module, just to make sure it's all working, with my own fork listed in my \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"package.json\"), \", before my changes get landed into AssemblyScript:\"), mdx(\"pre\", null, mdx(\"p\", {\n    className: \"title\"\n  }, \"package.json\"), mdx(\"code\", {\n    className: \"language-json\",\n    dangerouslySetInnerHTML: {\n      __html: \"{\\n  \u003cspan class=\\\"hljs-attr\\\"\u003e\u0026quot;devDependencies\u0026quot;\u003c/span\u003e: {\\n    \u003cspan class=\\\"hljs-attr\\\"\u003e\u0026quot;assemblyscript\u0026quot;\u003c/span\u003e: \u003cspan class=\\\"hljs-string\\\"\u003e\u0026quot;https://github.com/jerrygreen/assemblyscript/tarball/jerrygreen\u0026quot;\u003c/span\u003e\\n  }\\n}\"\n    }\n  })), mdx(\"h2\", null, \"Conclusion\"), mdx(\"p\", null, \"ECS is a neat architecture, but ECSY framework is poorly designed, non-performant, and basically unmaintained, so I came up with writing my own ECS implementation that fits my needs, fortunately the concept is real simple.\"), mdx(\"p\", null, \"Even if I trust the rendering part to some library rather than my custom \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RenderableSystem\"), \" class, I might still use the rest of my ECS implementation for calculating game logic, and even move it to server-side, to orchestrate all the world interactions for multiplayer, for example. Fortunately, no any \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RenderableSystem\"), \" needed on server, so basically no such bottleneck at all.\"), mdx(\"p\", null, \"I don't have much functionality in my game yet: still just a few flying circles and rectangles. I'm hoping to feature other interesting parts of the project I'm making. But for now...\"), mdx(\"p\", null, mdx(\"em\", {\n    parentName: \"p\"\n  }, \"That's it!\")));\n}\n\n;\nMDXContent.isMDXComponent = true;","renderedOutput":"\u003cblockquote\u003e\u003cp\u003eIn my \u003ca href=\"/blog/beginning-game-development\" rel=\"noopener\"\u003eprevious article\u003c/a\u003e I covered what\u0026#x27;s ECS in overall, and what\u0026#x27;s ECSY, some other tools and potential bottlenecks.\u003c/p\u003e\u003c/blockquote\u003e\u003cp\u003eIn this article I\u0026#x27;ll cover some in-depth benchmarks of what I built and play around it a bit.\u003c/p\u003e\u003cp\u003eBehind the scenes, before writing this article, I have quickly rewritten \u003ccode\u003e.js\u003c/code\u003e code examples to \u003ccode\u003e.ts\u003c/code\u003e and got rid of \u003ccode\u003eECSY\u003c/code\u003e library:\u003c/p\u003e\u003cimg width=\"700\" src=\"https://user-images.githubusercontent.com/13215662/112785062-9116f280-906c-11eb-9f1f-2b6cc18ce9ab.png\" alt=\"languages\" title=\"languages\" style=\"aspect-ratio:700 / 148\"/\u003e\u003cp\u003e90% of code is TypeScript now\u003c/p\u003e\u003cp\u003e\u003ccode\u003eECSY\u003c/code\u003e has neat concept but I figured out I don\u0026#x27;t like its internals and replaced it with my own ECS implementation. And even though my goal was just to get everything sorted and look good, clean, and typed, - it allowed me to achieve like at least x20 less GPU usage:\u003c/p\u003e\u003cimg width=\"490\" src=\"https://user-images.githubusercontent.com/13215662/112785064-93794c80-906c-11eb-917a-3e8b175a8139.png\" alt=\"frames\" title=\"frames\" style=\"aspect-ratio:490 / 185\"/\u003e\u003cp\u003ex20 less GPU usage now\u003c/p\u003e\u003cp\u003eI initially got bombuzled because I thought it\u0026#x27;s all on my CPU now, and I broke all the performance. Of course you can\u0026#x27;t notice the difference on such a simple example without hacky tricks: it runs smoothly at full 60fps in both cases. But with x6 throttling on CPU, I quickly realized I now have better CPU usage, too:\u003c/p\u003e\u003cimg width=\"490\" src=\"https://user-images.githubusercontent.com/13215662/112785066-95431000-906c-11eb-96cd-32883be63757.png\" alt=\"6x-throttle\" title=\"6x-throttle\" style=\"aspect-ratio:490 / 185\"/\u003e\u003cp\u003ex6 throttled Intel i7-9700, before and after: now it has less frame drops\u003c/p\u003e\u003cp\u003eSo it turns out clean code and types lead to performant applications and games. Who knew?\u003c/p\u003e\u003cp\u003eNow the project structure:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-bash\"\u003e. \u003cspan class=\"hljs-comment\"\u003e# root folder with all the config files\u003c/span\u003e\nsrc \u003cspan class=\"hljs-comment\"\u003e# folder with source code\u003c/span\u003e\nsrc/asm \u003cspan class=\"hljs-comment\"\u003e# experimental folder with AssemblyScript files that are later compiled into `.wasm` modules\u003c/span\u003e\nsrc/components \u003cspan class=\"hljs-comment\"\u003e# the first whale of ECS pattern: components\u003c/span\u003e\nsrc/elements \u003cspan class=\"hljs-comment\"\u003e# `.tsx` React Elements to render UI like buttons and menus\u003c/span\u003e\nsrc/entities \u003cspan class=\"hljs-comment\"\u003e# second whale of ECS pattern: entities\u003c/span\u003e\nsrc/game \u003cspan class=\"hljs-comment\"\u003e# the rest files related to game logic, like World.ts\u003c/span\u003e\nsrc/helpers \u003cspan class=\"hljs-comment\"\u003e# little utility functions\u003c/span\u003e\nsrc/main \u003cspan class=\"hljs-comment\"\u003e# electron \u0026quot;main process\u0026quot;-related files like background.ts and create-window.ts\u003c/span\u003e\nsrc/pages \u003cspan class=\"hljs-comment\"\u003e# all possible screens that I\u0026#x27;ll have in game, at this step is not actively used though\u003c/span\u003e\nsrc/systems \u003cspan class=\"hljs-comment\"\u003e# finally third whale of ECS pattern: systems\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSince components are basically javascript objects for data storage, my whole ECS implementation ended up with those three files:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-bash\"\u003esrc/game/Entity.ts \u003cspan class=\"hljs-comment\"\u003e# Base class for all entities to inherit\u003c/span\u003e\nsrc/game/System.ts \u003cspan class=\"hljs-comment\"\u003e# Base class for all systems to inherit\u003c/span\u003e\nsrc/game/World.ts \u003cspan class=\"hljs-comment\"\u003e# Almost like singleton-class, no inheritance needed\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eAnd instead of so-called ECS \u0026quot;Queries\u0026quot;, well... Have you heard about such a thing as objects? Hashes? Associative arrays, anyone? Why everything should be stored in classes? So I got rid of such class as \u0026quot;Component\u0026quot; and consecutively I no more need those weird \u0026quot;Queries\u0026quot;. Also, I don\u0026#x27;t store anything in another consuming data structure called \u0026quot;Collections\u0026quot; (basically arrays of objects). Collections are overrated and simply slow. So I use only plain arrays and nested objects, never an object inside an array (except for input arguments but never for storing). Pretty much like React, actually... This way I get beautiful syntax for accessing data out of the box.\u003c/p\u003e\u003cp\u003eSo instead of this awful API to extract components:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-ts\"\u003eentity.getComponent(Acceleration).acceleration\nentity.getComponent(Position).position\n\u003cspan class=\"hljs-comment\"\u003e// etc...\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eI can extract components much simpler now:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-ts\"\u003eentity.props.velocity\nentity.props.position\n\u003cspan class=\"hljs-comment\"\u003e// etc...\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eAnd the best part is that they\u0026#x27;re perfectly typed, so unlike in ECSY, I now have hints for the components and their data:\u003c/p\u003e\u003cimg width=\"630\" src=\"https://user-images.githubusercontent.com/13215662/110256402-8e1f6980-7fba-11eb-858f-18a087cd1096.PNG\" alt=\"types\" title=\"types\" style=\"aspect-ratio:630 / 121\"/\u003e\u003cp\u003eNow I have those precious hints\u003c/p\u003e\u003cp\u003eThose hints are based on each particular System, so it can only access those properties related to a system:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-ts\"\u003e\u003cspan class=\"hljs-keyword\"\u003econst\u003c/span\u003e Renderable = { ...Position, ...Shape } \u003cspan class=\"hljs-comment\"\u003e// \u0026lt;-- I can only access those components, related to the system\u003c/span\u003e\n\n\u003cspan class=\"hljs-keyword\"\u003eexport\u003c/span\u003e \u003cspan class=\"hljs-keyword\"\u003edefault\u003c/span\u003e \u003cspan class=\"hljs-class\"\u003e\u003cspan class=\"hljs-keyword\"\u003eclass\u003c/span\u003e \u003cspan class=\"hljs-title\"\u003eRenderableSystem\u003c/span\u003e \u003cspan class=\"hljs-keyword\"\u003eextends\u003c/span\u003e \u003cspan class=\"hljs-title\"\u003eSystem\u003c/span\u003e\u0026lt;\u003cspan class=\"hljs-title\"\u003etypeof\u003c/span\u003e \u003cspan class=\"hljs-title\"\u003eRenderable\u003c/span\u003e\u0026gt; \u003c/span\u003e{ ... }\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSo it turns out I don\u0026#x27;t need any special structures for querying data: JSON is my API. I guess this is one of the main reasons why my implementation got more lightweight and performant than ECSY framework.\u003c/p\u003e\u003cp\u003eBasically it\u0026#x27;s all just a web application on steroids, so in my root React component I use a couple React Hooks to do the following.\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-ts\"\u003e\u003cspan class=\"hljs-keyword\"\u003econst\u003c/span\u003e world = useMemo(\u003cspan class=\"hljs-function\"\u003e() =\u0026gt;\u003c/span\u003e {\n  \u003cspan class=\"hljs-keyword\"\u003eif\u003c/span\u003e (ctx) {\n    \u003cspan class=\"hljs-keyword\"\u003econst\u003c/span\u003e wd = \u003cspan class=\"hljs-keyword\"\u003enew\u003c/span\u003e World({\n      \u003cspan class=\"hljs-attr\"\u003esystems\u003c/span\u003e: [\n        \u003cspan class=\"hljs-comment\"\u003e/* World initialized with these listed systems */\u003c/span\u003e\n        \u003cspan class=\"hljs-comment\"\u003e/* It is able to fill them up dynamically later */\u003c/span\u003e\n      ],\n      \u003cspan class=\"hljs-attr\"\u003eentities\u003c/span\u003e: [\n        \u003cspan class=\"hljs-comment\"\u003e/* Might initialize world 
83with some entities */\u003c/span\u003e\n        \u003cspan class=\"hljs-comment\"\u003e/* Also able to add them dynamically later */\u003c/span\u003e\n      ],\n    })\n    \u003cspan class=\"hljs-keyword\"\u003ereturn\u003c/span\u003e wd\n  }\n}, [ctx])\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eAnd this is how game loop gets started, when world is initialized:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-ts\"\u003euseEffect(\u003cspan class=\"hljs-function\"\u003e() =\u0026gt;\u003c/span\u003e {\n  \u003cspan class=\"hljs-keyword\"\u003eif\u003c/span\u003e (world) {\n    \u003cspan class=\"hljs-built_in\"\u003econsole\u003c/span\u003e.info(\u003cspan class=\"hljs-string\"\u003e\u0026#x27;Game loaded!\u0026#x27;\u003c/span\u003e)\n    world.run(performance.now(), setFps)\n  }\n}, [world])\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eAt this point we\u0026#x27;re basically out of React paradigm, and can make all the logic inside our ECS paradigm.\u003c/p\u003e\u003cp\u003eI can still use React to render whatever UI on top of the game, accessing systems, entities, their components and data. Which is kinda cool: React for user interface menus and buttons, ECS for game loop and continuous game logic handling.\u003c/p\u003e\u003ch2\u003eIs ECSY dead?\u003c/h2\u003e\u003cp\u003eI heard \u003ca href=\"https://github.com/ecsyjs/ecsy/issues/279\" rel=\"noopener\" target=\"_blank\"\u003ethis question on GitHub\u003c/a\u003e, and the answer tends to be \u0026quot;yes\u0026quot;, - in addition to all the performance problems and awful syntax it involves, it seems to be unmaintained too.\u003c/p\u003e\u003cp\u003eI initially liked ECSY but quickly realized I like the ECS pattern, not the ECSY library.\u003c/p\u003e\u003cp\u003eSome people still try to adopt it by making things like \u003ca href=\"https://github.com/DavidPeicho/ecstra\" rel=\"noopener\" target=\"_blank\"\u003eecstra\u003c/a\u003e, though I\u0026#x27;m sceptic about it, since it\u0026#x27;s still based on ECSY with all its weird queries and component-classes.\u003c/p\u003e\u003cp\u003eEven with custom ECS implementation, I still face the same problem I mentioned in my \u003ca href=\"/blog/beginning-game-development\" rel=\"noopener\"\u003eprevious article\u003c/a\u003e though: \u003ccode\u003eRenderableSystem\u003c/code\u003e. It\u0026#x27;s very naive and still re-draws the whole screen entirely as before, with no optimizations, so it\u0026#x27;s still the bottleneck of whatever engine I\u0026#x27;m coming up with, and still upon my attentive observation. First candidate for replacement.\u003c/p\u003e\u003ch2\u003eWhat\u0026#x27;s about AssemblyScript?\u003c/h2\u003e\u003cp\u003eI currently still have no use to that basically, but I have plans on it, and I\u0026#x27;m preparing some ground for it. I made a couple Pull Requests so it can be actually usable to me, with all the conveniences I\u0026#x27;m used to. Like TypeScript hints: \u003ca href=\"https://github.com/AssemblyScript/assemblyscript/pull/1705\" rel=\"noopener\" target=\"_blank\"\u003eAssemblyScript#1705\u003c/a\u003e, and globs: \u003ca href=\"https://github.com/AssemblyScript/assemblyscript/pull/1716\" rel=\"noopener\" target=\"_blank\"\u003eAssemblyScript#1716\u003c/a\u003e. I\u0026#x27;m using previously mentioned \u003ccode\u003e1 + 2 = 3\u003c/code\u003e little WASM module, just to make sure it\u0026#x27;s all working, with my own fork listed in my \u003ccode\u003epackage.json\u003c/code\u003e, before my changes get landed into AssemblyScript:\u003c/p\u003e\u003cpre\u003e\u003cp class=\"title\"\u003epackage.json\u003c/p\u003e\u003ccode class=\"language-json\"\u003e{\n  \u003cspan class=\"hljs-attr\"\u003e\u0026quot;devDependencies\u0026quot;\u003c/span\u003e: {\n    \u003cspan class=\"hljs-attr\"\u003e\u0026quot;assemblyscript\u0026quot;\u003c/span\u003e: \u003cspan class=\"hljs-string\"\u003e\u0026quot;https://github.com/jerrygreen/assemblyscript/tarball/jerrygreen\u0026quot;\u003c/span\u003e\n  }\n}\u003c/code\u003e\u003c/pre\u003e\u003ch2\u003eConclusion\u003c/h2\u003e\u003cp\u003eECS is a neat architecture, but ECSY framework is poorly designed, non-performant, and basically unmaintained, so I came up with writing my own ECS implementation that fits my needs, fortunately the concept is real simple.\u003c/p\u003e\u003cp\u003eEven if I trust the rendering part to some library rather than my custom \u003ccode\u003eRenderableSystem\u003c/code\u003e class, I might still use the rest of my ECS implementation for calculating game logic, and even move it to server-side, to orchestrate all the world interactions for multiplayer, for example. Fortunately, no any \u003ccode\u003eRenderableSystem\u003c/code\u003e needed on server, so basically no such bottleneck at all.\u003c/p\u003e\u003cp\u003eI don\u0026#x27;t have much functionality in my game yet: still just a few flying circles and rectangles. I\u0026#x27;m hoping to feature other interesting parts of the project I\u0026#x27;m making. But for now...\u003c/p\u003e\u003cp\u003e\u003cem\u003eThat\u0026#x27;s it!\u003c/em\u003e\u003c/p\u003e","scope":{}},"imagePreview":null},"beginning-game-development":{"id":"ddf66ef2c1e5e42550367fcb4d4c3d23","path":"beginning-game-development","title":"Beginning Game Development with TypeScript and ECS","description":"I recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here's what I came up with…","stargazerCount":2,"commentsCount":0,"gistRepo":"https://gist.github.com/ddf
8366ef2c1e5e42550367fcb4d4c3d23","createdAt":"2021-03-25T22:29:33Z","updatedAt":"2021-09-21T12:19:57Z","readingTime":{"text":"10 min read","minutes":9.716666666666667,"time":583000,"words":1166},"mdx":"# Beginning Game Development with TypeScript and ECS\n\nI recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here's what I came up with…\n\n\u003c!--\n\nReserved\nfor preview\n\n--\u003e\n\n\u003e DISCLAIMER: Should not be considered as a \"tutorial\", though it might give you inspiration to do your own thing. Read this article for your entertainment!\n\nI decided to use some of the knowledge I already had, so I ended up with this list of tools to experiment with, for a project start:\n\n- [Electron](https://www.electronjs.org/). It can let me experiment with rendering and see frame drops, and even throttle my cpu to see if my game performant or not, with many other development tools, with ability to pack a game into executable.\n- [React](https://reactjs.org/). A convenient way to render user interfaces: menus, buttons, and that's pretty much it.\n- [TypeScript](https://www.typescriptlang.org/). It is an extension/superset of JavaScript and has cool typing system that helps during development to write reliable code.\n- [AssemblyScript](https://www.assemblyscript.org/). It is a strict subset of TypeScript that compiles into performant [WebAssembly](https://webassembly.org/), which can be useful for some hard tasks like procedural world generation and such, to decrease loading times.\n- [ECSY](https://ecsy.io/). Entity-Component-System (ECS) pattern looks real cool for a game architecture. ECSY is a library that might help me sort out my game stuff into distinguishable parts for readability and maintainability. It is made by team from Mozilla, so I liked ECSY as soon as I found it.\n\nThis is how it looks right now:\n\n![game@x2](https://user-images.githubusercontent.com/13215662/109962442-0103b880-7d0d-11eb-8986-1995b3378779.gif 'Circles and rectangles for now')\n\nFor now it's barely a game, and more like fundament for a game. It has game loop, some additional development infrastru
83cture like unit tests, some build scripts to make executables for different platforms, etc.\n\nMy root React component looks very minimalistic right now:\n\n```tsx\n\u003c\u003e\n  \u003cHead\u003e\n    \u003ctitle\u003eProject X\u003c/title\u003e\n  \u003c/Head\u003e\n  \u003cdiv id=\"overlay\"\u003e\n    \u003cp\u003e\n      \u003cLink href=\"/next\"\u003e\n        \u003ca\u003eGo to next page\u003c/a\u003e\n      \u003c/Link\u003e\n    \u003c/p\u003e\n    \u003cp\u003eExecute function from wasm module:\u003c/p\u003e\n    \u003cp\u003e\n      1 + 2 = \u003cAsmComponent /\u003e\n    \u003c/p\u003e\n  \u003c/div\u003e\n  \u003cGame /\u003e {/* \u003c---- this is where circles and rectangles live */}\n\u003c/\u003e\n```\n\nYou might wonder what is this \"Go to next page\", - but it's just a link to a blank screen, not currently used. Later on, I can add some main menu screen, settings screen, and some other. React is still neat to build such interfaces. For the main `Game` part, though, ECSY is used.\n\n`AsmComponent` is currently just a little WebAssembly component so I can check that everything works as intended which uses this AssemblyScript code:\n\n```as.ts add\nexport function add(a: i32, b: i32): i32 {\n  return a + b\n}\n```\n\nLooks very much like TypeScript, though it has some limitations and cannot use all the TypeScript features. Full feature comparison can be found [here](https://www.assemblyscript.org/status.html#webassembly-features).\n\nThe good side of AssemblyScript that it can run certain programs much faster. A friend of mine made a benchmark by implementing [\"Conway's Game of Life\" in JS and AS](https://refruity.xyz/assembly-script/), and he figured out AssemblyScript version is more than 2x performant than JavaScript! Might be quite useful for game development in JS/TS… So I might use some AssemblyScript for world generation or something like that, later.\n\nI won't paste whole `engine.js` which I shamelessly stolen from official ECSY examples, but here's the part responsible for game loop:\n\n```js\nexport async function run(lastTime) {\n  const time = performance.now()\n  var delta = time - lastTime\n  const fps = 1000 / delta\n  world.fps = fps\n  world.execute(delta, time)\n  requestAnimationFrame(async () =\u003e run(time))\n}\n```\n\n`world.execute` is a tricky fella here… `execute` function makes all the changes to the world: updating positions, where re-rendering and other stuff is happening. It is the most heavy part here, and it has budget of 16.67 milliseconds to do all that, to maintain 60 fps in the game. Needless to say, it should be implemented in most cool optimized way.\n\n## Entity Component System\n\nIt is the blood of my game. Again, I use little `ECSY` library that helps me arrange all that. It has three main elements, and a couple other:\n\n- **Entity**. It is basically any thing in the game: `Human`, or some `Sheep`, or `Plant`, `Tree`, whatever… Each entity has an unique ID and consists of many Components. In programming words, entities are classes and instances.\n- **Component**. It is basically data: an object, hash, associative array, you name it. Some property that an entity can have, or multiple. Something can be `EdibleComponent`, or `HealthComponent` so an entity can have hp and die, some other components may be related purely to rendering like `ShadowComponent`. Components can be composed and applied to an entity. Weirdly, in ECSY it's all stored in class instances, but anyway…\n- **System**. It is code. It runs perpetually in game loop, and has all the logic regarding different components. In many cases components have their corresponding system, for example for HealthComponent there likely to be `HealthSystem` which may iterate through mortal creatures to check if they're out of health to actually kill them and trigger some death animation, etc. You can guess what does `ShadowingSystem`, `CollisionSystem`, and other.\n- **World**. A container to store all that. Basically one is enough, but it is possible to have many worlds simultaneously.\n- **Query**. Queries are used by Systems to filter those particular entities that they're interested in, based on some property/component entities own\n\nDetailed ECS architecture with pictures can be found [here](https://ecsy.io/docs/#/manual/Architecture?id=overview). It's quite neat.\n\n## 3D vs 2D\n\nIn my particular example I don't have any 3D yet, but I have no limits on that, and can potentially add 3D models. It will be harder ofc, not only because 3D models are harder to do than 2D images, but it will also require some dynamic lightning and maybe some other stuff, which might be not easy to do. I'm hoping to embrace the power of 3D, actually, at some point.\n\n## What to improve\n\nCurrently I have my own `RenderableSystem` which re-draws entire screen from scratch on each frame. Even though it runs smoothly with this simple rectangles-circles example at 60fps, it might not work as well in real world examples. Again, to draw everything, the budget is very limited: 16.67 milliseconds. So `RenderableSystem` might come out to be the bottleneck for making a good performant game. I'll keep an eye on that. But for now…\n\n_That's it!_\n","contentPreview":{"compiledSource":"\"use strict\";\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i \u003c arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i \u003c sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i \u003c sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar layoutProps = {};\nvar MDXLayout = \"wrapper\";\n\nfunction MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"I recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here's what I came up with\\u2026\"));\n}\n\n;\nMDXContent.isMDXComponent = true;","renderedOutput":"\u003cp\u003eI recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here\u0026#x27;s what I came up with…\u003c/p\u003e","scope":{}},"content":{"compiledSource":"\"use strict\";\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i \u003c arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i \u003c sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i \u003c sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) \u003e= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar layoutProps = {};\nvar MDXLayout = \"wrapper\";\n\nfunction MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"I recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here's what I came up with\\u2026\"), mdx(\"blockquote\", null, mdx(\"p\", {\n    parentName: \"blockquote\"\n  }, \"DISCLAIMER: Should not be considered as a \\\"tutorial\\\", though it might give you inspiration to do your own thing. Read this article for your entertainment!\")), mdx(\"p\", null, \"I decided to use some of the knowledge I already had, so I ended up with this list of tools to experiment with, for a project start:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://www.electronjs.org/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"Electron\"), \". It can let me experiment with rendering and see frame drops, and even throttle my cpu to see if my game performant or not, with many other development tools, with ability to pack a game into executable.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://reactjs.org/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"React\"), \". A convenient way to render user interfaces: menus, buttons, and that's pretty much it.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }
83, mdx(\"a\", {\n    href: \"https://www.typescriptlang.org/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"TypeScript\"), \". It is an extension/superset of JavaScript and has cool typing system that helps during development to write reliable code.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://www.assemblyscript.org/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"AssemblyScript\"), \". It is a strict subset of TypeScript that compiles into performant \", mdx(\"a\", {\n    href: \"https://webassembly.org/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"WebAssembly\"), \", which can be useful for some hard tasks like procedural world generation and such, to decrease loading times.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    href: \"https://ecsy.io/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"ECSY\"), \". Entity-Component-System (ECS) pattern looks real cool for a game architecture. ECSY is a library that might help me sort out my game stuff into distinguishable parts for readability and maintainability. It is made by team from Mozilla, so I liked ECSY as soon as I found it.\")), mdx(\"p\", null, \"This is how it looks right now:\"), mdx(\"img\", {\n    width: \"854\",\n    src: \"https://user-images.githubusercontent.com/13215662/109962442-0103b880-7d0d-11eb-8986-1995b3378779.gif\",\n    alt: \"game\",\n    title: \"game\",\n    style: {\n      \"aspectRatio\": \"854 / 480\"\n    }\n  }), mdx(\"p\", null, \"Circles and rectangles for now\"), mdx(\"p\", null, \"For now it's barely a game, and more like fundament for a game. It has game loop, some additional development infrastru
83cture like unit tests, some build scripts to make executables for different platforms, etc.\"), mdx(\"p\", null, \"My root React component looks very minimalistic right now:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-tsx\",\n    dangerouslySetInnerHTML: {\n      __html: \"\u0026lt;\u0026gt;\\n  \u003cspan class=\\\"xml\\\"\u003e\u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003eHead\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n    \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003etitle\u003c/span\u003e\u0026gt;\u003c/span\u003eProject X\u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003etitle\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n  \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003eHead\u003c/span\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\\n  \u003cspan class=\\\"xml\\\"\u003e\u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003ediv\u003c/span\u003e \u003cspan class=\\\"hljs-attr\\\"\u003eid\u003c/span\u003e=\u003cspan class=\\\"hljs-string\\\"\u003e\u0026quot;overlay\u0026quot;\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n    \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n      \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003eLink\u003c/span\u003e \u003cspan class=\\\"hljs-attr\\\"\u003ehref\u003c/span\u003e=\u003cspan class=\\\"hljs-string\\\"\u003e\u0026quot;/next\u0026quot;\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n        \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003ea\u003c/span\u003e\u0026gt;\u003c/span\u003eGo to next page\u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003ea\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n      \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003eLink\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n    \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n    \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003eExecute function from wasm module:\u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n    \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n      1 + 2 = \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003eAsmComponent\u003c/span\u003e /\u0026gt;\u003c/span\u003e\\n    \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\\n  \u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;/\u003cspan class=\\\"hljs-name\\\"\u003ediv\u003c/span\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\\n  \u003cspan class=\\\"xml\\\"\u003e\u003cspan class=\\\"hljs-tag\\\"\u003e\u0026lt;\u003cspan class=\\\"hljs-name\\\"\u003eGame\u003c/span\u003e /\u0026gt;\u003c/span\u003e\u003c/span\u003e {\u003cspan class=\\\"hljs-comment\\\"\u003e/* \u0026lt;---- this is where circles and rectangles live */\u003c/span\u003e}\\n\u0026lt;/\u0026gt;\"\n    }\n  })), mdx(\"p\", null, \"You might wonder what is this \\\"Go to next page\\\", - but it's just a link to a blank screen, not currently used. Later on, I can add some main menu screen, settings screen, and some other. React is still neat to build such interfaces. For the main \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Game\"), \" part, though, ECSY is used.\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"AsmComponent\"), \" is currently just a little WebAssembly component so I can check that everything works as intended which uses this AssemblyScript code:\"), mdx(\"pre\", null, mdx(\"p\", {\n    className: \"title\"\n  }, \"add.as.ts\"), mdx(\"code\", {\n    className: \"language-as.ts\",\n    dangerouslySetInnerHTML: {\n      __html: \"\u003cspan class=\\\"hljs-keyword\\\"\u003eexport\u003c/span\u003e \u003cspan class=\\\"hljs-function\\\"\u003e\u003cspan class=\\\"hljs-keyword\\\"\u003efunction\u003c/span\u003e \u003cspan class=\\\"hljs-title\\\"\u003eadd\u003c/span\u003e(\u003cspan class=\\\"hljs-params\\\"\u003ea: i32, b: i32\u003c/span\u003e): \u003cspan class=\\\"hljs-title\\\"\u003ei32\u003c/span\u003e \u003c/span\u003e{\\n  \u003cspan class=\\\"hljs-keyword\\\"\u003ereturn\u003c/span\u003e a + b\\n}\"\n    }\n  })), mdx(\"p\", null, \"Looks very much like TypeScript, though it has some limitations and cannot use all the TypeScript features. Full feature comparison can be found \", mdx(\"a\", {\n    href: \"https://www.assemblyscript.org/status.html#webassembly-features\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"here\"), \".\"), mdx(\"p\", null, \"The good side of AssemblyScript that it can run certain programs much faster. A friend of mine made a benchmark by implementing \", mdx(\"a\", {\n    href: \"https://refruity.xyz/assembly-script/\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"\\\"Conway's Game of Life\\\" in JS and AS\"), \", and he figured out AssemblyScript version is more than 2x performant than JavaScript! Might be quite useful for game development in JS/TS\\u2026 So I might use some AssemblyScript for world generation or something like that, later.\"), mdx(\"p\", null, \"I won't paste whole \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"engine.js\"), \" which I shamelessly stolen from official ECSY examples, but here's the part responsible for game loop:\"), mdx(\"pre\", null, mdx(\"code\", {\n    className: \"language-js\",\n    dangerouslySetInnerHTML: {\n      __html: \"\u003cspan class=\\\"hljs-keyword\\\"\u003eexport\u003c/span\u003e \u003cspan class=\\\"hljs-keyword\\\"\u003easync\u003c/span\u003e \u003cspan class=\\\"hljs-function\\\"\u003e\u003cspan class=\\\"hljs-keyword\\\"\u003efunction\u003c/span\u003e \u003cspan class=\\\"hljs-title\\\"\u003erun\u003c/span\u003e(\u003cspan class=\\\"hljs-params\\\"\u003elastTime\u003c/span\u003e) \u003c/span\u003e{\\n  \u003cspan class=\\\"hljs-keyword\\\"\u003econst\u003c/span\u003e time = performance.now()\\n  \u003cspan class=\\\"hljs-keyword\\\"\u003evar\u003c/span\u003e delta = time - lastTime\\n  \u003cspan class=\\\"hljs-keyword\\\"\u003econst\u003c/span\u003e fps = \u003cspan class=\\\"hljs-number\\\"\u003e1000\u003c/span\u003e / delta\\n  world.fps = fps\\n  world.execute(delta, time)\\n  requestAnimationFrame(\u003cspan class=\\\"hljs-keyword\\\"\u003easync\u003c/span\u003e () =\u0026gt; run(time))\\n}\"\n    }\n  })), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"world.execute\"), \" is a tricky fella here\\u2026 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"execute\"), \" function makes all the changes to the world: updating positions, where re-rendering and other stuff is happening. It is the most heavy part here, and it has budget of 16.67 milliseconds to do all that, to maintain 60 fps in the game. Needless to say, it should be implemented in most cool optimized way.\"), mdx(\"h2\", null, \"Entity Component System\"), mdx(\"p\", null, \"It is the blood of my game. Again, I use little \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ECSY\"), \" library that helps me arrange all that. It has three main elements, and a couple other:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Entity\"), \". It is basically any thing in the game: \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Human\"), \", or some \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Sheep\"), \", or \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Plant\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Tree\"), \", whatever\\u2026 Each entity has an unique ID and consists of many Components. In programming words, entities are classes and instances.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Component\"), \". It is basically data: an object, hash, associative array, you name it. Some property that an entity can have, or multiple. Something can be \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"EdibleComponent\"), \", or \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HealthComponent\"), \" so an entity can have hp and die, some other components may be related purely to rendering like \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ShadowComponent\"), \". Components can be composed and applied to an entity. Weirdly, in ECSY it's all stored in class instances, but anyway\\u2026\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"System\"), \". It is code. It runs perpetually in game loop, and has all the logic regarding different c
83omponents. In many cases components have their corresponding system, for example for HealthComponent there likely to be \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"HealthSystem\"), \" which may iterate through mortal creatures to check if they're out of health to actually kill them and trigger some death animation, etc. You can guess what does \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ShadowingSystem\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"CollisionSystem\"), \", and other.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"World\"), \". A container to store all that. Basically one is enough, but it is possible to have many worlds simultaneously.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Query\"), \". Queries are used by Systems to filter those particular entities that they're interested in, based on some property/component entities own\")), mdx(\"p\", null, \"Detailed ECS architecture with pictures can be found \", mdx(\"a\", {\n    href: \"https://ecsy.io/docs/#/manual/Architecture?id=overview\",\n    rel: \"noopener\",\n    target: \"_blank\"\n  }, \"here\"), \". It's quite neat.\"), mdx(\"h2\", null, \"3D vs 2D\"), mdx(\"p\", null, \"In my particular example I don't have any 3D yet, but I have no limits on that, and can potentially add 3D models. It will be harder ofc, not only because 3D models are harder to do than 2D images, but it will also require some dynamic lightning and maybe some other stuff, which might be not easy to do. I'm hoping to embrace the power of 3D, actually, at some point.\"), mdx(\"h2\", null, \"What to improve\"), mdx(\"p\", null, \"Currently I have my own \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RenderableSystem\"), \" which re-draws entire screen from scratch on each frame. Even though it runs smoothly with this simple rectangles-circles example at 60fps, it might not work as well in real world examples. Again, to draw everything, the budget is very limited: 16.67 milliseconds. So \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"RenderableSystem\"), \" might come out to be the bottleneck for making a good performant game. I'll keep an eye on that. But for now\\u2026\"), mdx(\"p\", null, mdx(\"em\", {\n    parentName: \"p\"\n  }, \"That's it!\")));\n}\n\n;\nMDXContent.isMDXComponent = true;","renderedOutput":"\u003cp\u003eI recently started developing a game, while not using any huge game engines like Unity of Unreal Engine, nor even smaller ones like Heaps or PhaserJS or Cocos2D game frameworks. Here\u0026#x27;s what I came up with…\u003c/p\u003e\u003cblockquote\u003e\u003cp\u003eDISCLAIMER: Should not be considered as a \u0026quot;tutorial\u0026quot;, though it might give you inspiration to do your own thing. Read this article for your entertainment!\u003c/p\u003e\u003c/blockquote\u003e\u003cp\u003eI decided to use some of the knowledge I already had, so I ended up with this list of tools to experiment with, for a project start:\u003c/p\u003e\u003cul\u003e\u003cli\u003e\u003ca href=\"https://www.electronjs.org/\" rel=\"noopener\" target=\"_blank\"\u003eElectron\u003c/a\u003e. It can let me experiment with rendering and see frame drops, and even throttle my cpu to see if my game performant or not, with many other development tools, with ability to pack a game into executable.\u003c/li\u003e\u003cli\u003e\u003ca href=\"https://reactjs.org/\" rel=\"noopener\" target=\"_blank\"\u003eReact\u003c/a\u003e. A convenient way to render user interfaces: menus, buttons, and that\u0026#x27;s pretty much it.\u003c/li\u003e\u003cli\u003e\u003ca href=\"https://www.typescriptlang.org/\" rel=\"noopener\" target=\"_blank\"\u003eTypeScript\u003c/a\u003e. It is an extension/superset of JavaScript and has cool typing system that helps during development to write reliable code.\u003c/li\u003e\u003cli\u003e\u003ca href=\"https://www.assemblyscript.org/\" rel=\"noopener\" target=\"_blank\"\u003eAssemblyScript\u003c/a\u003e. It is a strict subset of TypeScript that compiles into performant \u003ca href=\"https://webassembly.org/\" rel=\"noopener\" target=\"_blank\"\u003eWebAssembly\u003c/a\u003e, which can be useful for some hard tasks like procedural world generation and such, to decrease loading times.\u003c/li\u003e\u003cli\u003e\u003ca href=\"https://ecsy.io/\" rel=\"noopener\" target=\"_blank\"\u003eECSY\u003c/a\u003e. Entity-Component-System (ECS) pattern looks real cool for a game architecture. ECSY is a library that might help me sort out my game stuff into distinguishable parts for readability and maintainability. It is made by team from Mozilla, so I liked ECSY as soon as I foun
83d it.\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eThis is how it looks right now:\u003c/p\u003e\u003cimg width=\"854\" src=\"https://user-images.githubusercontent.com/13215662/109962442-0103b880-7d0d-11eb-8986-1995b3378779.gif\" alt=\"game\" title=\"game\" style=\"aspect-ratio:854 / 480\"/\u003e\u003cp\u003eCircles and rectangles for now\u003c/p\u003e\u003cp\u003eFor now it\u0026#x27;s barely a game, and more like fundament for a game. It has game loop, some additional development infrastructure like unit tests, some build scripts to make executables for different platforms, etc.\u003c/p\u003e\u003cp\u003eMy root React component looks very minimalistic right now:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-tsx\"\u003e\u0026lt;\u0026gt;\n  \u003cspan class=\"xml\"\u003e\u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003eHead\u003c/span\u003e\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003etitle\u003c/span\u003e\u0026gt;\u003c/span\u003eProject X\u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003etitle\u003c/span\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003eHead\u003c/span\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n  \u003cspan class=\"xml\"\u003e\u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003ediv\u003c/span\u003e \u003cspan class=\"hljs-attr\"\u003eid\u003c/span\u003e=\u003cspan class=\"hljs-string\"\u003e\u0026quot;overlay\u0026quot;\u003c/span\u003e\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003eLink\u003c/span\u003e \u003cspan class=\"hljs-attr\"\u003ehref\u003c/span\u003e=\u003cspan class=\"hljs-string\"\u003e\u0026quot;/next\u0026quot;\u003c/span\u003e\u0026gt;\u003c/span\u003e\n        \u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003ea\u003c/span\u003e\u0026gt;\u003c/span\u003eGo to next page\u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003ea\u003c/span\u003e\u0026gt;\u003c/span\u003e\n      \u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003eLink\u003c/span\u003e\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003eExecute function from wasm module:\u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\n      1 + 2 = \u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003eAsmComponent\u003c/span\u003e /\u0026gt;\u003c/span\u003e\n    \u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003ep\u003c/span\u003e\u0026gt;\u003c/span\u003e\n  \u003cspan class=\"hljs-tag\"\u003e\u0026lt;/\u003cspan class=\"hljs-name\"\u003ediv\u003c/span\u003e\u0026gt;\u003c/span\u003e\u003c/span\u003e\n  \u003cspan class=\"xml\"\u003e\u003cspan class=\"hljs-tag\"\u003e\u0026lt;\u003cspan class=\"hljs-name\"\u003eGame\u003c/span\u003e /\u0026gt;\u003c/span\u003e\u003c/span\u003e {\u003cspan class=\"hljs-comment\"\u003e/* \u0026lt;---- this is where circles and rectangles live */\u003c/span\u003e}\n\u0026lt;/\u0026gt;\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eYou might wonder what is this \u0026quot;Go to next page\u0026quot;, - but it\u0026#x27;s just a link to a blank screen, not currently used. Later on, I can add some main menu screen, settings screen, and some other. React is still neat to build such interfaces. For the main \u003ccode\u003eGame\u003c/code\u003e part, though, ECSY is used.\u003c/p\u003e\u003cp\u003e\u003ccode\u003eAsmComponent\u003c/code\u003e is currently just a little WebAssembly component so I can check that everything works as intended which uses this AssemblyScript code:\u003c/p\u003e\u003cpre\u003e\u003cp class=\"title\"\u003eadd.as.ts\u003c/p\u003e\u003ccode class=\"language-as.ts\"\u003e\u003cspan class=\"hljs-keyword\"\u003eexport\u003c/span\u003e \u003cspan class=\"hljs-function\"\u003e\u003cspan class=\"hljs-keyword\"\u003efunction\u003c/span\u003e \u003cspan class=\"hljs-title\"\u003eadd\u003c/span\u003e(\u003cspan class=\"hljs-params\"\u003ea: i32, b: i32\u003c/span\u003e): \u003cspan class=\"hljs-title\"\u003ei32\u003c/span\u003e \u003c/span\u003e{\n  \u003cspan class=\"hljs-keyword\"\u003ereturn\u003c/span\u003e a + b\n}\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eLooks very much like TypeScript, though it has some limitations and cannot use all the TypeScript features. Full feature comparison can be found \u003ca href=\"https://www.assemblyscript.org/status.html#webassembly-features\" rel=\"noopener\" target=\"_blank\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\u003cp\u003eThe good side of AssemblyScript that it can run certain programs much faster. A friend of mine made a benchmark by implementing \u003ca href=\"https://refruity.xyz/assembly-script/\" rel=\"noopener\" target=\"_blank\"\u003e\u0026quot;Conway\u0026#x27;s Game of Life\u0026quot; in JS and AS\u003c/a\u003e, and he figured out AssemblyScript version is more than 2x performant than JavaScript! Might be quite useful for game development in JS/TS… So I might use some AssemblyScript for world generation or something like that, later.\u003c/p\u003e\u003cp\u003eI won\u0026#x27;t paste whole \u003ccode\u003eengine.js\u003c/code\u003e which I shamelessly stolen from official ECSY examples, but here\u0026#x27;s the part responsible for game loop:\u003c/p\u003e\u003cpre\u003e\u003ccode class=\"language-js\"\u003e\u003cspan class=\"hljs-keyword\"\u003eexport\u003c/span\u003e \u003cspan class=\"hljs-keyword\"\u003easync\u003c/span\u003e \u003cspan class=\"hljs-function\"\u003e\u003cspan class=\"hljs-keyword\"\u003efunction\u003c/span\u003e \u003cspan class=\"hljs-title\"\u003erun\u003c/span\u003e(\u003cspan class=\"hljs-params\"\u003elastTime\u003c/span\u003e) \u003c/span\u003e{\n  \u003cspan class=\"hljs-keyword\"\u003econst\u003c/span\u003e time = performance.now()\n  \u003cspan class=\"hljs-keyword\"\u003evar\u003c/span\u003e delta = time - lastTime\n  \u003cspan class=\"hljs-keyword\"\u003econst\u003c/span\u003e fps = \u003cspan class=\"hljs-number\"\u003e1000\u003c/span\u003e / delta\n  world.fps = fps\n  world.execute(delta, time)\n  requestAnimationFrame(\u003cspan class=\"hljs-keyword\"\u003easync\u003c/span\u003e () =\u0026gt; run(time))\n}\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ccode\u003eworld.execute\u003c/code\u003e is a tricky fella here… \u
83003ccode\u003eexecute\u003c/code\u003e function makes all the changes to the world: updating positions, where re-rendering and other stuff is happening. It is the most heavy part here, and it has budget of 16.67 milliseconds to do all that, to maintain 60 fps in the game. Needless to say, it should be implemented in most cool optimized way.\u003c/p\u003e\u003ch2\u003eEntity Component System\u003c/h2\u003e\u003cp\u003eIt is the blood of my game. Again, I use little \u003ccode\u003eECSY\u003c/code\u003e library that helps me arrange all that. It has three main elements, and a couple other:\u003c/p\u003e\u003cul\u003e\u003cli\u003e\u003cstrong\u003eEntity\u003c/strong\u003e. It is basically any thing in the game: \u003ccode\u003eHuman\u003c/code\u003e, or some \u003ccode\u003eSheep\u003c/code\u003e, or \u003ccode\u003ePlant\u003c/code\u003e, \u003ccode\u003eTree\u003c/code\u003e, whatever… Each entity has an unique ID and consists of many Components. In programming words, entities are classes and instances.\u003c/li\u003e\u003cli\u003e\u003cstrong\u003eComponent\u003c/strong\u003e. It is basically data: an object, hash, associative array, you name it. Some property that an entity can have, or multiple. Something can be \u003ccode\u003eEdibleComponent\u003c/code\u003e, or \u003ccode\u003eHealthComponent\u003c/code\u003e so an entity can have hp and die, some other components may be related purely to rendering like \u003ccode\u003eShadowComponent\u003c/code\u003e. Components can be composed and applied to an entity. Weirdly, in ECSY it\u0026#x27;s all stored in class instances, but anyway…\u003c/li\u003e\u003cli\u003e\u003cstrong\u003eSystem\u003c/strong\u003e. It is code. It runs perpetually in game loop, and has all the logic regarding different components. In many cases components have their corresponding system, for example for HealthComponent there likely to be \u003ccode\u003eHealthSystem\u003c/code\u003e which may iterate through mortal creatures to check if they\u0026#x27;re out of health to actually kill them and trigger some death animation, etc. You can guess what does \u003ccode\u003eShadowingSystem\u003c/code\u003e, \u003ccode\u003eCollisionSystem\u003c/code\u003e, and other.\u003c/li\u003e\u003cli\u003e\u003cstrong\u003eWorld\u003c/strong\u003e. A container to store all that. Basically one is enough, but it is possible to have many worlds simultaneously.\u003c/li\u003e\u003cli\u003e\u003cstrong\u003eQuery\u003c/strong\u003e. Queries are used by Systems to filter those particular entities that they\u0026#x27;re interested in, based on some property/component entities own\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eDetailed ECS architecture with pictures can be found \u003ca href=\"https://ecsy.io/docs/#/manual/Architecture?id=overview\" rel=\"noopener\" target=\"_blank\"\u003ehere\u003c/a\u003e. It\u0026#x27;s quite neat.\u003c/p\u003e\u003ch2\u003e3D vs 2D\u003c/h2\u003e\u003cp\u003eIn my particular example I don\u0026#x27;t have any 3D yet, but I have no limits on that, and can potentially add 3D models. It will be harder ofc, not only because 3D models are harder to do than 2D images, but it will also require some dynamic lightning and maybe some other stuff, which might be not easy to do. I\u0026#x27;m hoping to embrace the power of 3D, actually, at some point.\u003c/p\u003e\u003ch2\u003eWhat to improve\u003c/h2\u003e\u003cp\u003eCurrently I have my own \u003ccode\u003eRenderableSystem\u003c/code\u003e which re-draws entire screen from scratch on each frame. Even though it runs smoothly with this simple rectangles-circles example at 60fps, it might not work as well in real world examples. Again, to draw everything, the budget is very limited: 16.67 milliseconds. So \u003ccode\u003eRenderableSystem\u003c/code\u003e might come out to be the bottleneck for making a good performant game. I\u0026#x27;ll keep an eye on that. But for now…\u003c/p\u003e\u003cp\u003e\u003cem\u003eThat\u0026#x27;s it!\u003c/em\u003e\u003c/p\u003e","scope":{}},"imagePreview":null}}},"__N_SSG":true},"page":"/blog","query":{},"buildId":"rU2bz8ehDP4HOjO1vJnjK","runtimeConfig":{},"nextExport":false,"isFallback":false,"gsp":true}</script>
83<script nomodule="" src="/_next/static/chunks/polyfills-04ff97a05ed484bd5f0e.js"></script>
83<script src="/_next/static/chunks/main-2202f484d6914ceb4d8e.js" async=""></script>
83<script src="/_next/static/chunks/webpack-50bee04d1dc61f8adf5b.js" async=""></script>
83<script src="/_next/static/chunks/framework.8dfdf69b72dfc2029ab9.js" async=""></script>
83<script src="/_next/static/chunks/8f79dc18.7f06bc6c2c3f10c57bd0.js" async=""></script>
83<script src="/_next/static/chunks/commons.b78e9ab41aa75bdf260d.js" async=""></script>
83<script src="/_next/static/chunks/5fe5d2636d30c84a90ce481a26ac21e9514f7953.8221c3accfa506bee6c0.js" async=""></script>
83<script src="/_next/static/chunks/pages/_app-333ec2a757a7f61002ec.js" async=""></script>
83<script src="/_next/static/chunks/0e226fb0.00458a403201668feaaf.js" async=""></script>
83<script src="/_next/static/chunks/134b6bfdd40f1c0473727571e3cbe8fd291287bb.cc4fa7855a83014ee0b1.js" async=""></script>
83<script src="/_next/static/chunks/2798d23c72fc1fa6b330024ccd47ee9972611cff.0192114cbb8db4c687e0.js" async=""></script>
83<script src="/_next/static/chunks/2abca662105608a5fed85b4aa2c3215d764b09cb.b6715c3567e61bb74677.js" async=""></script>
83<script src="/_next/static/chunks/4fe3a15990720369bf83f599385448bc626ca7a2.412b0268891fc4011179.js" async=""></script>
83<script src="/_next/static/chunks/pages/blog-1da48ee7703faa2f5fbd.js" async=""></script>
83<script src="/_next/static/rU2bz8ehDP4HOjO1vJnjK/_buildManifest.js" async=""></script>
83<script src="/_next/static/rU2bz8ehDP4HOjO1vJnjK/_ssgManifest.js" async=""></script>
83</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.