webring 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/__snapshots__/index.test.ts.snap +0 -7
- package/src/testdata/rss-1.xml +0 -2135
- package/src/testdata/rss-10.xml +0 -1029
- package/src/testdata/rss-11.xml +0 -881
- package/src/testdata/rss-12.xml +0 -4193
- package/src/testdata/rss-13.xml +0 -518
- package/src/testdata/rss-14.xml +0 -155
- package/src/testdata/rss-15.xml +0 -731
- package/src/testdata/rss-16.xml +0 -44
- package/src/testdata/rss-17.xml +0 -120
- package/src/testdata/rss-18.xml +0 -50
- package/src/testdata/rss-19.xml +0 -690
- package/src/testdata/rss-2.xml +0 -2186
- package/src/testdata/rss-3.xml +0 -80356
- package/src/testdata/rss-4.xml +0 -1601
- package/src/testdata/rss-5.xml +0 -3991
- package/src/testdata/rss-6.xml +0 -825
- package/src/testdata/rss-7.xml +0 -35
- package/src/testdata/rss-8.xml +0 -12516
- package/src/testdata/rss-9.xml +0 -369
package/src/testdata/rss-1.xml
DELETED
|
@@ -1,2135 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
3
|
-
<channel>
|
|
4
|
-
<title>Jerred's Blog</title>
|
|
5
|
-
<description>My personal blog</description>
|
|
6
|
-
<link>https://sjer.red/</link>
|
|
7
|
-
<item>
|
|
8
|
-
<title>TIL: Using Twoslash with Shiki and Astro</title>
|
|
9
|
-
<link>https://sjer.red/blog/til/2024-07-01/</link>
|
|
10
|
-
<guid isPermaLink="true">https://sjer.red/blog/til/2024-07-01/</guid>
|
|
11
|
-
<description>TIL: Using Twoslash with Shiki and Astro</description>
|
|
12
|
-
<pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate>
|
|
13
|
-
<content:encoded><p>Do you want code snippets like below on your Astro site?</p>
|
|
14
|
-
<p>Note: you can hover over types to see their definitions.</p>
|
|
15
|
-
<pre><code>interface IdLabel {
|
|
16
|
-
id: number /* some fields */;
|
|
17
|
-
}
|
|
18
|
-
interface NameLabel {
|
|
19
|
-
name: string /* other fields */;
|
|
20
|
-
}
|
|
21
|
-
type NameOrId&lt;T extends number | string&gt; = T extends number ? IdLabel :
|
|
22
|
-
NameLabel;
|
|
23
|
-
// This comment should not be included
|
|
24
|
-
|
|
25
|
-
// ---cut---
|
|
26
|
-
function createLabel&lt;T extends number | string&gt;(idOrName: T):
|
|
27
|
-
NameOrId&lt;T&gt; {
|
|
28
|
-
throw "unimplemented";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let a = createLabel("typescript");
|
|
32
|
-
</code></pre>
|
|
33
|
-
<p>It's super easy. In your <code>astro.config.ts</code> file, add
|
|
34
|
-
the following:</p>
|
|
35
|
-
<pre><code>import { defineConfig } from "astro/config";
|
|
36
|
-
// ---cut---
|
|
37
|
-
import { rendererRich, transformerTwoslash } from "@shikijs/twoslash";
|
|
38
|
-
|
|
39
|
-
export default defineConfig({
|
|
40
|
-
markdown: {
|
|
41
|
-
shikiConfig: {
|
|
42
|
-
transformers: [
|
|
43
|
-
transformerTwoslash({
|
|
44
|
-
renderer: rendererRich(),
|
|
45
|
-
}),
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
</code></pre>
|
|
51
|
-
<p>Import this CSS in your layout:</p>
|
|
52
|
-
<pre><code>import "@shikijs/twoslash/style-rich.css";
|
|
53
|
-
</code></pre>
|
|
54
|
-
<p>Add the following CSS and import it in your layout:</p>
|
|
55
|
-
<pre><code>// fixes an issue where type popups are cut off
|
|
56
|
-
.astro-code {
|
|
57
|
-
overflow: visible !important;
|
|
58
|
-
}
|
|
59
|
-
</code></pre>
|
|
60
|
-
<p>Bonus: enable an automatic light &amp; dark mode. Add the following CSS from
|
|
61
|
-
<a
|
|
62
|
-
href="https://shiki.style/guide/dual-themes#query-based-dark-mode">Shiki's
|
|
63
|
-
documentation</a>:</p>
|
|
64
|
-
<pre><code>@media (prefers-color-scheme: dark) {
|
|
65
|
-
.shiki,
|
|
66
|
-
.shiki span {
|
|
67
|
-
color: var(--shiki-dark) !important;
|
|
68
|
-
background-color: var(--shiki-dark-bg) !important;
|
|
69
|
-
/* Optional, if you also want font styles */
|
|
70
|
-
font-style: var(--shiki-dark-font-style) !important;
|
|
71
|
-
font-weight: var(--shiki-dark-font-weight) !important;
|
|
72
|
-
text-decoration: var(--shiki-dark-text-decoration) !important;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
</code></pre>
|
|
76
|
-
<p>Add the following to your <code>astro.config.ts</code>:</p>
|
|
77
|
-
<pre><code>import { defineConfig } from "astro/config";
|
|
78
|
-
// ---cut---
|
|
79
|
-
export default defineConfig({
|
|
80
|
-
markdown: {
|
|
81
|
-
shikiConfig: {
|
|
82
|
-
theme: "github-dark",
|
|
83
|
-
themes: {
|
|
84
|
-
light: "github-light",
|
|
85
|
-
dark: "github-dark",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
</code></pre>
|
|
91
|
-
<p>You can try it on this site by toggling your browser's or operating
|
|
92
|
-
system's dark mode.</p>
|
|
93
|
-
<p>Check out <a
|
|
94
|
-
href="https://shikijs.github.io/twoslash/">Shiki's Twoslash
|
|
95
|
-
documentation</a> for details.</p>
|
|
96
|
-
</content:encoded>
|
|
97
|
-
</item>
|
|
98
|
-
<item>
|
|
99
|
-
<title>Homelab 1 - Setting up K3s</title>
|
|
100
|
-
<link>https://sjer.red/blog/2024/homelab-1/</link>
|
|
101
|
-
<guid isPermaLink="true">https://sjer.red/blog/2024/homelab-1/</guid>
|
|
102
|
-
<description>Setting up K3s</description>
|
|
103
|
-
<pubDate>Wed, 26 Jun 2024 00:00:00 GMT</pubDate>
|
|
104
|
-
<content:encoded><p>This series of posts will detail the setup of my homelab. It goes
|
|
105
|
-
into the technical details of how I setup a single-node K3s Kubernetes cluster using cdk8s
|
|
106
|
-
and Deno to generate all of the required YAML manifests. This is a practical deployment
|
|
107
|
-
with:</p>
|
|
108
|
-
<ul>
|
|
109
|
-
<li>Automated backups</li>
|
|
110
|
-
<li>Monitoring and alerting</li>
|
|
111
|
-
<li>Automated deployments</li>
|
|
112
|
-
<li>Automatic image/chart upgrades</li>
|
|
113
|
-
<li>Support for GPU acceleration</li>
|
|
114
|
-
<li>Secure secrets with 1Password</li>
|
|
115
|
-
<li>Secure remote access through Tailscale</li>
|
|
116
|
-
<li>Direct access for game servers and certain protocols like mDNS</li>
|
|
117
|
-
</ul>
|
|
118
|
-
<h2>Table of Contents</h2>
|
|
119
|
-
<h2>Background</h2>
|
|
120
|
-
<p>I've had a homelab for around a decade. The hardware itself has gone from
|
|
121
|
-
repurposed parts in college (a Core Duo served me very well from 2017-2022) to a very beefy
|
|
122
|
-
server today:</p>
|
|
123
|
-
<ul>
|
|
124
|
-
<li><a
|
|
125
|
-
href="https://pcpartpicker.com/product/ZLjRsY/intel-core-i9-14900k-32-ghz-24-core-processor-bx8071514900k">Intel
|
|
126
|
-
Core i9-14900K 3.2 GHz 24-Core</a></li>
|
|
127
|
-
<li>2 x <a
|
|
128
|
-
href="https://pcpartpicker.com/product/J2zXsY/corsair-vengeance-32-gb-2-x-16-gb-ddr5-5600-cl40-memory-cmk32gx5m2b5600c40">Corsair
|
|
129
|
-
Vengeance 32 GB (2 x 16 GB) DDR5-5600</a></li>
|
|
130
|
-
<li><a
|
|
131
|
-
href="https://pcpartpicker.com/product/VWxRsY/samsung-990-pro-heatsink-4-tb-m2-2280-pcie-40-x4-nvme-solid-state-drive-mz-v9p4t0cw">Samsung
|
|
132
|
-
990 Pro 4 TB</a></li>
|
|
133
|
-
<li>5 x <a
|
|
134
|
-
href="https://pcpartpicker.com/product/jD3H99/seagate-barracuda-4tb-35-5400rpm-internal-hard-drive-st4000dm004">Seagate
|
|
135
|
-
BarraCuda 4 TB 5400 RPM</a></li>
|
|
136
|
-
</ul>
|
|
137
|
-
<p>(The full build is on <a
|
|
138
|
-
href="https://pcpartpicker.com/user/RiotShielder/saved/#view=bnTM3C">PCPartPicker</a>)</p>
|
|
139
|
-
<p>Over the years I've tried quite a few ways to manage it:</p>
|
|
140
|
-
<ul>
|
|
141
|
-
<li>Manually installing everything without any automation</li>
|
|
142
|
-
<li>Artisinal, hand-written bash scripts</li>
|
|
143
|
-
<li>Ansible</li>
|
|
144
|
-
<li>Docker Compose</li>
|
|
145
|
-
</ul>
|
|
146
|
-
<p>Those methods were mostly informed by what I was wanting to learn. That trend
|
|
147
|
-
hasn't changed with my move to Kubernetes. I had no experience with K8s back in
|
|
148
|
-
December, and today I use it to manage my homelab quite successfully. Kubernetes is overkill
|
|
149
|
-
for a homelab, but it does provide a great learning environment for me where the
|
|
150
|
-
consequences are relatively low (as long as my backups keep working).</p>
|
|
151
|
-
<p>I name each iteration of my server so that I can disambiguate between references of
|
|
152
|
-
older installations. Previously I named my servers after Greek/Roman gods, but now I'm
|
|
153
|
-
using the names of famous computer scientists. The name of the latest iteration is
|
|
154
|
-
"lamport", named after <a
|
|
155
|
-
href="https://en.wikipedia.org/wiki/Leslie_Lamport">Leslie Lamport</a>
|
|
156
|
-
who is known for his work in distributed systems.</p>
|
|
157
|
-
<h2>Operating System</h2>
|
|
158
|
-
<p>Because I was planning on running everything in Kubernetes, I considered using
|
|
159
|
-
<a href="https://www.talos.dev/">Talos</a> rather than my usual choice
|
|
160
|
-
of Ubuntu Server. Talos is an immutable Linux distribution with support for Kubernetes
|
|
161
|
-
baked-in. I ultimately didn't choose Talos because at the time required a dedicated
|
|
162
|
-
drive for the operating system, meaning my entire 4TB SSD would be taken up by
|
|
163
|
-
Talos.</p>
|
|
164
|
-
<p>I ultimately chose Ubuntu Server which has served me well for years despite the
|
|
165
|
-
community seeming to be unhappy with some of the recent choices by Canonical. I didn't
|
|
166
|
-
do anything special for my installation other than setting up my RAID 5 array with
|
|
167
|
-
<code>mdadm</code> and <code>fstab</code>.</p>
|
|
168
|
-
<h2>Kubernetes</h2>
|
|
169
|
-
<p>Similar to how there are multiple flavors of Linux distributions, there are many
|
|
170
|
-
distributios of Kubernetes. I chose to use <a
|
|
171
|
-
href="https://k3s.io/">K3s</a> since it has a reputation of being
|
|
172
|
-
lightweight, easy-to-use, and stable.</p>
|
|
173
|
-
<p>You can configure K3s by creating a file at
|
|
174
|
-
<code>/etc/rancher/k3s/config.yaml</code>. There are some options that
|
|
175
|
-
<em>cannot</em> be changed after K3s is installed. The one most relevant to me
|
|
176
|
-
is IPv6 support. Be sure to look through the <a
|
|
177
|
-
href="https://docs.k3s.io/advanced">configuration options</a>.</p>
|
|
178
|
-
<p>Installation is straightforward:</p>
|
|
179
|
-
<pre><code>$ curl -sfL https://get.k3s.io | sh -
|
|
180
|
-
</code></pre>
|
|
181
|
-
<p>And... that's it! You now have a Kubernetes cluster running on your machine.
|
|
182
|
-
You can use <code>kubectl</code> to interact with your cluster. I also use <a
|
|
183
|
-
href="https://aptakube.com/">Aptakube</a> on my MacBook when I want a GUI
|
|
184
|
-
for monitoring my cluster.</p>
|
|
185
|
-
<p>I use <a href="https://tailscale.com/">Tailscale</a> to
|
|
186
|
-
securely access my homelab. Tailscale is a Wireguard-based VPN that's, quite honestly,
|
|
187
|
-
fun to use. You can use your favorite VPN to access your homelab, or if you're brave
|
|
188
|
-
you can expose it to the public internet.</p>
|
|
189
|
-
<p>Credentials for your cluster are stored at
|
|
190
|
-
<code>/etc/rancher/k3s/k3s.yaml</code>. You can copy this file to
|
|
191
|
-
<code>~/.kube/config</code> on your local machine to use
|
|
192
|
-
<code>kubectl</code>. Note: you'll also need to change the
|
|
193
|
-
<code>server</code> field to point to the address of your server.</p>
|
|
194
|
-
<h2>Bootstrapping</h2>
|
|
195
|
-
<p>This does require a small amount of manual bootstrapping, which I describe in my
|
|
196
|
-
<a
|
|
197
|
-
href="https://github.com/shepherdjerred/homelab/blob/main/README.md">repository
|
|
198
|
-
README</a>. Whenever I setup a new cluster/node, I need to:</p>
|
|
199
|
-
<ul>
|
|
200
|
-
<li>Install ArgoCD: <code>kubectl apply -n argocd -f
|
|
201
|
-
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml</code></li>
|
|
202
|
-
<li>Create a secrets to access my 1Password vaults</li>
|
|
203
|
-
<li>Deploy the manifests in this repo: <code>kubectl apply -f
|
|
204
|
-
cdk8s/dist/apps.k8s.yaml</code></li>
|
|
205
|
-
</ul>
|
|
206
|
-
<p>I've recreated my cluster a couple of times and I've been very pleased
|
|
207
|
-
with how easy it is to get everything back up and running. It takes me just a few minutes
|
|
208
|
-
from installing K3s to having all of my services back up and running.</p>
|
|
209
|
-
<h2>Conclusion</h2>
|
|
210
|
-
<p>I've shown how I setup my cluster with K3s. In future posts I'll
|
|
211
|
-
cover:</p>
|
|
212
|
-
<ul>
|
|
213
|
-
<li>Using Deno and cdk8s to generate manifests</li>
|
|
214
|
-
<li>Automating deployments with ArgoCD</li>
|
|
215
|
-
<li>Ingress with HTTPS and Tailscale</li>
|
|
216
|
-
<li>Direct connections to pods</li>
|
|
217
|
-
<li>mDNS</li>
|
|
218
|
-
<li>Persistent volumes</li>
|
|
219
|
-
<li>Backups</li>
|
|
220
|
-
<li>Monitoring</li>
|
|
221
|
-
<li>Helm, Kustomize, and operators</li>
|
|
222
|
-
<li>Keeping things up-to-date</li>
|
|
223
|
-
</ul>
|
|
224
|
-
</content:encoded>
|
|
225
|
-
</item>
|
|
226
|
-
<item>
|
|
227
|
-
<title>TIL: Asymmetric Cryptography in Go</title>
|
|
228
|
-
<link>https://sjer.red/blog/til/2024-06-05/</link>
|
|
229
|
-
<guid isPermaLink="true">https://sjer.red/blog/til/2024-06-05/</guid>
|
|
230
|
-
<description>TIL: Asymmetric Cryptography in Go</description>
|
|
231
|
-
<pubDate>Wed, 05 Jun 2024 00:00:00 GMT</pubDate>
|
|
232
|
-
<content:encoded><p>I've been implementing a feature at work that involves
|
|
233
|
-
asymmetric cryptography. It has been a pretty fun exercise in stitching together Go APIs
|
|
234
|
-
while reading about best practices.</p>
|
|
235
|
-
<p>Here's a few things I've learned over the last couple of days:</p>
|
|
236
|
-
<ul>
|
|
237
|
-
<li>
|
|
238
|
-
<p>Go's cryptography <a
|
|
239
|
-
href="https://kupczynski.info/posts/fips-golang/">isn't FIPS
|
|
240
|
-
compliant</a>.</p>
|
|
241
|
-
</li>
|
|
242
|
-
<li>
|
|
243
|
-
<p>Go has an implementation of <a
|
|
244
|
-
href="https://pkg.go.dev/crypto/ecdsa">ECDSA</a> (Elliptic Curve Digital
|
|
245
|
-
Signature Algorithm), but it doesn't have any elliptic curve asymmetric encryption
|
|
246
|
-
algorithms.</p>
|
|
247
|
-
<ul>
|
|
248
|
-
<li>The best asymmetric algorithm that Go has is <a
|
|
249
|
-
href="https://pkg.go.dev/crypto/rsa">RSA</a></li>
|
|
250
|
-
</ul>
|
|
251
|
-
</li>
|
|
252
|
-
<li>
|
|
253
|
-
<p>Go has an implementation of <a
|
|
254
|
-
href="https://pkg.go.dev/encoding/pem">PEM</a> (Privacy Enhanced Mail)
|
|
255
|
-
data encoding which can be used to encode public/private in a familiar format. You've
|
|
256
|
-
probably seen this format with SSH keys:</p>
|
|
257
|
-
<pre><code>-----BEGIN PUBLIC KEY-----
|
|
258
|
-
MIIEpAIBAAKCAQEAuOuUOwNRMbqc0jMEVTOyKuVUu0bk0zD5iwIggBHpDhV58DSJ
|
|
259
|
-
SK7OFIFHVMy6FKg2B3Y50srfVJ45OE9Vsb9hfErUNA/PB5meHGEI+yPKeni4GAfy
|
|
260
|
-
&lt;and so on&gt;
|
|
261
|
-
-----END PUBLIC KEY-----
|
|
262
|
-
</code></pre>
|
|
263
|
-
</li>
|
|
264
|
-
<li>
|
|
265
|
-
<p>The <a href="https://www.rfc-editor.org/rfc/rfc1421">legacy PEM
|
|
266
|
-
format</a> has support for plaintext headers like so:</p>
|
|
267
|
-
<pre><code>-----BEGIN PUBLIC KEY-----
|
|
268
|
-
Data: Some value I don't mind being plaintext
|
|
269
|
-
MIIEpAIBAAKCAQEAuOuUOwNRMbqc0jMEVTOyKuVUu0bk0zD5iwIggBHpDhV58DSJ
|
|
270
|
-
SK7OFIFHVMy6FKg2B3Y50srfVJ45OE9Vsb9hfErUNA/PB5meHGEI+yPKeni4GAfy
|
|
271
|
-
&lt;and so on&gt;
|
|
272
|
-
-----END PUBLIC KEY-----
|
|
273
|
-
</code></pre>
|
|
274
|
-
<ul>
|
|
275
|
-
<li>The <a href="https://www.rfc-editor.org/rfc/rfc7468">newer
|
|
276
|
-
RFC</a> eplicitly doesn't support headers, though:
|
|
277
|
-
<blockquote>
|
|
278
|
-
<p>Unlike legacy PEM encoding <a
|
|
279
|
-
href="https://www.rfc-editor.org/rfc/rfc1421">RFC1421</a>, OpenPGP ASCII
|
|
280
|
-
armor, and the
|
|
281
|
-
OpenSSH key file format, textual encoding does <em>not</em> define or permit
|
|
282
|
-
headers to be encoded alongside the data.</p>
|
|
283
|
-
</blockquote>
|
|
284
|
-
</li>
|
|
285
|
-
</ul>
|
|
286
|
-
</li>
|
|
287
|
-
<li>
|
|
288
|
-
<p>Go's APIs for encrypting, decrypting, signing, and verifying data are quite
|
|
289
|
-
pleasant to use!</p>
|
|
290
|
-
<ul>
|
|
291
|
-
<li>The <a href="https://pkg.go.dev/crypto/rsa#pkg-examples">Go
|
|
292
|
-
examples</a> illustrate this quite well.</li>
|
|
293
|
-
</ul>
|
|
294
|
-
</li>
|
|
295
|
-
<li>
|
|
296
|
-
<p>When signing data, Go will first have you run that data through a hash algorithm
|
|
297
|
-
(e.g. <a href="https://pkg.go.dev/crypto/sha256">SHA256</a>). This
|
|
298
|
-
actually makes quite a bit of sense, and it helps me better understand why secure hashing is
|
|
299
|
-
important for cryptography.</p>
|
|
300
|
-
</li>
|
|
301
|
-
<li>
|
|
302
|
-
<p>OWASP (Open Worldwide Application Security Project) has a great section on <a
|
|
303
|
-
href="https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#algorithms">encryption
|
|
304
|
-
algorithms</a> which can help guide those less familiar with the specifics of
|
|
305
|
-
encryption.</p>
|
|
306
|
-
</li>
|
|
307
|
-
<li>
|
|
308
|
-
<p>There are a few algorithms for signing and encryption data with RSA. Go implements
|
|
309
|
-
PKCS1v15 and OAEP for encryption, and PKCS1v15 and PSS for signing.</p>
|
|
310
|
-
<ul>
|
|
311
|
-
<li>This <a
|
|
312
|
-
href="https://security.stackexchange.com/questions/183179/what-is-rsa-oaep-rsa-pss-in-simple-terms/183330#183330">Stack
|
|
313
|
-
Exchange answer</a> goes into the details of these algorithms.</li>
|
|
314
|
-
</ul>
|
|
315
|
-
</li>
|
|
316
|
-
</ul>
|
|
317
|
-
<p>While I'm generally not a huge fan of Go, I do think the standard library has
|
|
318
|
-
some nice packages, and the encryption library is definitely one of them.</p>
|
|
319
|
-
</content:encoded>
|
|
320
|
-
</item>
|
|
321
|
-
<item>
|
|
322
|
-
<title>TIL: Closures in Groovy</title>
|
|
323
|
-
<link>https://sjer.red/blog/til/2024-05-24/</link>
|
|
324
|
-
<guid isPermaLink="true">https://sjer.red/blog/til/2024-05-24/</guid>
|
|
325
|
-
<description>TIL: Closures in Groovy</description>
|
|
326
|
-
<pubDate>Fri, 24 May 2024 00:00:00 GMT</pubDate>
|
|
327
|
-
<content:encoded><p>import Socratic from "../../../components/Socratic.astro";
|
|
328
|
-
import Dialog from "../../../components/Dialog.astro";
|
|
329
|
-
import Divider from "../../../components/Divider.astro";</p>
|
|
330
|
-
<p>I interact with <a href="https://groovy-lang.org/">Groovy</a>
|
|
331
|
-
solely because <a href="https://www.jenkins.io/">Jenkins</a> uses it
|
|
332
|
-
to define pipelines.</p>
|
|
333
|
-
<p>Pros:</p>
|
|
334
|
-
<ul>
|
|
335
|
-
<li>It's in the Java ecosystem</li>
|
|
336
|
-
<li>It's a "real" language (turing complete unlike YAML)</li>
|
|
337
|
-
<li>It's not YAML</li>
|
|
338
|
-
</ul>
|
|
339
|
-
<p>Cons:</p>
|
|
340
|
-
<ul>
|
|
341
|
-
<li>No static typing</li>
|
|
342
|
-
<li>Linters/formatters aren't great</li>
|
|
343
|
-
<li>Jenkins is quite hard to work with and has surprisingly poor tooling</li>
|
|
344
|
-
</ul>
|
|
345
|
-
<p>While updating some Groovy scripts, I wanted to update a utiltiy method to take a
|
|
346
|
-
closure in a nicer way. The method looked like this:</p>
|
|
347
|
-
<pre><code>def hello(clo, first = "John", last = "Doe") {
|
|
348
|
-
println "Hello, $first $last"
|
|
349
|
-
clo()
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// usage
|
|
353
|
-
foo(clo: { println "I didn't see you there!" }, first: "Jerred",
|
|
354
|
-
last: "Shepherd")
|
|
355
|
-
</code></pre>
|
|
356
|
-
<p>I've seen methods that look a bit prettier when called, like this:</p>
|
|
357
|
-
<pre><code>def bar(someArg, Closure clo) {
|
|
358
|
-
clo()
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
bar(someArg) {
|
|
362
|
-
println 'hello'
|
|
363
|
-
}
|
|
364
|
-
</code></pre>
|
|
365
|
-
<p>Note that the closure is passed as the last argument. This allows the closure to be
|
|
366
|
-
passed in braces after the method call. This looks quite a bit nicer!</p>
|
|
367
|
-
<p>&lt;Dialog&gt;
|
|
368
|
-
&lt;Socratic perspective="student"&gt;
|
|
369
|
-
It looks like Groovy has some nice syntatic sugar: named and default parameters are both
|
|
370
|
-
pretty nice to have.
|
|
371
|
-
&lt;/Socratic&gt;
|
|
372
|
-
&lt;Divider /&gt;
|
|
373
|
-
&lt;Socratic perspective="teacher"&gt;
|
|
374
|
-
Groovy does have some nice features, but{" "}
|
|
375
|
-
&lt;a
|
|
376
|
-
href="https://groovy-lang.org/objectorientation.html#_named_parameters"&gt;named
|
|
377
|
-
parameters&lt;/a&gt; have some rough
|
|
378
|
-
edges. The documentation doesn't clearly cover how to use named parameters with default
|
|
379
|
-
parameters or closures.
|
|
380
|
-
&lt;/Socratic&gt;
|
|
381
|
-
&lt;/Dialog&gt;</p>
|
|
382
|
-
<p>Originally, I wanted to combine named parameters, default parameters, and a closure
|
|
383
|
-
as the last argument. Unfortunately, this doesn't seem possible. Here's what I
|
|
384
|
-
came up with:</p>
|
|
385
|
-
<pre><code>def baz(first = "John", last = "Doe", Closure
|
|
386
|
-
clo) {
|
|
387
|
-
println "Hello, $first $last"
|
|
388
|
-
clo()
|
|
389
|
-
}
|
|
390
|
-
</code></pre>
|
|
391
|
-
<p>The result of running this:</p>
|
|
392
|
-
<pre><code>baz(first: "Jerred", last: "Shepherd") {
|
|
393
|
-
println "I didn't see you there!"
|
|
394
|
-
}
|
|
395
|
-
Hello, [first:Jerred, last:Shepherd] Doe
|
|
396
|
-
I didn't see you there!
|
|
397
|
-
</code></pre>
|
|
398
|
-
<p>Groovy implements named parameters as a map. Unfortunately, it seems that Groovy is
|
|
399
|
-
using the both the <code>first</code> and <code>last</code>
|
|
400
|
-
parameters as a map value and passing that to the <code>first</code>
|
|
401
|
-
argument.</p>
|
|
402
|
-
<pre><code>// Groovy takes this:
|
|
403
|
-
baz(first: "Jerred", last: "Shepherd")
|
|
404
|
-
|
|
405
|
-
// and implicitly converts it to this:
|
|
406
|
-
baz([first: "Jerred", last: "Shepherd"], null)
|
|
407
|
-
|
|
408
|
-
// so, when calling baz, we're passing the map as the first argument and null as the
|
|
409
|
-
second argument
|
|
410
|
-
// this leads to Groovy using the map as the first argument, and the default value of
|
|
411
|
-
"Doe" as the second argument
|
|
412
|
-
Hello, [first:Jerred, last:Shepherd] Doe
|
|
413
|
-
I didn't see you there!
|
|
414
|
-
|
|
415
|
-
// instead, we want Groovy to print:
|
|
416
|
-
Hello, Jerred Shepherd
|
|
417
|
-
I didn't see you there!
|
|
418
|
-
</code></pre>
|
|
419
|
-
<p>Closures are still pretty cool, but it's frustrating to figure out the syntax
|
|
420
|
-
for how some of these features interact. Additionally, the feedback loop for Groovy with
|
|
421
|
-
Jenkins is long, so if you don't know the language, it's hard to make
|
|
422
|
-
progress.</p>
|
|
423
|
-
</content:encoded>
|
|
424
|
-
</item>
|
|
425
|
-
<item>
|
|
426
|
-
<title>Compilers Project Setup</title>
|
|
427
|
-
<link>https://sjer.red/blog/2024/compilers-setup/</link>
|
|
428
|
-
<guid isPermaLink="true">https://sjer.red/blog/2024/compilers-setup/</guid>
|
|
429
|
-
<description>My 8803 Compilers Project Setup</description>
|
|
430
|
-
<pubDate>Tue, 23 Jan 2024 00:00:00 GMT</pubDate>
|
|
431
|
-
<content:encoded><p>This semester I'm taking <a
|
|
432
|
-
href="https://omscs.gatech.edu/cs-8803-o08-compilers-theory-and-practice">CS
|
|
433
|
-
8803 Compilers at Georgia Tech</a>. I've heard this class is quite challenging,
|
|
434
|
-
but so far I've had a blast and cannot wait to learn more.</p>
|
|
435
|
-
<p>The class has you build a compiler across several phases. It's unique in that
|
|
436
|
-
it sets <em>very</em> few constraints for your implementation. The only real
|
|
437
|
-
technical requirement is that you use C++ and Java, and <a
|
|
438
|
-
href="https://www.antlr.org">ANTLR</a> for the front-end.</p>
|
|
439
|
-
<p>All of this freedom leaves a lot of questions for students, especially if
|
|
440
|
-
they're not used to writing large applications by themselves.</p>
|
|
441
|
-
<p>I'm sharing some of the decisions I've made for my project. Almost all of
|
|
442
|
-
this is specific to Java, so if you're using C++, you will find little to help
|
|
443
|
-
you.</p>
|
|
444
|
-
<p>Let's get to it! If you have any questions, I'd be happy to answer them.
|
|
445
|
-
Shoot me an email: <a
|
|
446
|
-
href="mailto:compilers@sjer.red">compilers@sjer.red</a>.</p>
|
|
447
|
-
<h2>Maven</h2>
|
|
448
|
-
<p>You should absolutely be using Maven, especially considering class explicitly
|
|
449
|
-
supports it. Maven makes your life <em>much</em> easier. It handles all of your
|
|
450
|
-
dependencies and the lifecycle of building and tests your project.</p>
|
|
451
|
-
<p>Maven is still a fairly relevant technology in the Java world today. It surpassed
|
|
452
|
-
<a href="https://ant.apache.org">Ant</a>, and it still a very popular
|
|
453
|
-
choice for projects today. The more modern alternative is <a
|
|
454
|
-
href="https://gradle.org">Gradle</a>. You might be able to use Gradle,
|
|
455
|
-
but you'd have to ask the course staff.</p>
|
|
456
|
-
<p>Here's my full <a
|
|
457
|
-
href="https://gist.github.com/shepherdjerred/d36b1815f50be9fc2b03f686989987e5"><code>pom.xml</code></a>
|
|
458
|
-
so that you can see how I've set all of this up.</p>
|
|
459
|
-
<h2>Earthly</h2>
|
|
460
|
-
<p><a href="https://earthly.dev">Earthly</a> is one of my
|
|
461
|
-
favorite tools. It's essentially a combination of Docker and Make. You define a set of
|
|
462
|
-
targets. Each instruction is run in a containerized environment. This gives you the
|
|
463
|
-
simplicity and ergonomics of Make, with all the isolation benefits of Docker.</p>
|
|
464
|
-
<p>I use Earthly to build the <code>.zip</code> that I submit, and to
|
|
465
|
-
ensure that the <code>.zip</code> is buildable in the Docker environment that
|
|
466
|
-
Gradescope uses.</p>
|
|
467
|
-
<p>Here's what my Earthfile looks like:</p>
|
|
468
|
-
<pre><code># This declares the version of Earthly to use
|
|
469
|
-
VERSION 0.7
|
|
470
|
-
|
|
471
|
-
# This builds the Dockerfile that the course staff provides
|
|
472
|
-
image:
|
|
473
|
-
FROM DOCKERFILE resources/docker/
|
|
474
|
-
WORKDIR /workspace/
|
|
475
|
-
|
|
476
|
-
# Download Maven dependencies
|
|
477
|
-
deps:
|
|
478
|
-
FROM +image
|
|
479
|
-
COPY pom.xml .
|
|
480
|
-
CACHE /root/.m2/repository/
|
|
481
|
-
RUN mvn dependency:resolve
|
|
482
|
-
RUN rm pom.xml
|
|
483
|
-
|
|
484
|
-
# Zip up my submission. Save the .zip file as a local artifact.
|
|
485
|
-
zip:
|
|
486
|
-
FROM ubuntu:jammy
|
|
487
|
-
WORKDIR /workspace/
|
|
488
|
-
|
|
489
|
-
RUN apt update
|
|
490
|
-
RUN apt install -y zip
|
|
491
|
-
|
|
492
|
-
COPY src/main/ src/main/
|
|
493
|
-
COPY pom.xml lombok.config Makefile .
|
|
494
|
-
COPY +tiger/Tiger.g4 .
|
|
495
|
-
RUN zip -r submission.zip .
|
|
496
|
-
|
|
497
|
-
SAVE ARTIFACT submission.zip AS LOCAL submission.zip
|
|
498
|
-
|
|
499
|
-
# Unzip my submission an attempt to build it.
|
|
500
|
-
build:
|
|
501
|
-
FROM +deps
|
|
502
|
-
COPY +zip/submission.zip .
|
|
503
|
-
RUN unzip submission.zip
|
|
504
|
-
RUN make all
|
|
505
|
-
SAVE ARTIFACT cs8803_bin/tigerc.jar AS LOCAL cs8803_bin/tigerc.jar
|
|
506
|
-
|
|
507
|
-
# I specify my Tiger Lexer and Parser in separate files, within a directory that is idomatic
|
|
508
|
-
for ANTLR
|
|
509
|
-
# The project PDF says that a single Tiger.g4 file must exist at the root of the project,
|
|
510
|
-
which is what this target creates.
|
|
511
|
-
tiger:
|
|
512
|
-
FROM ubuntu:jammy
|
|
513
|
-
COPY src/main/antlr4/com/shepherdjerred/compiler/TigerLexer.g4 .
|
|
514
|
-
COPY src/main/antlr4/com/shepherdjerred/compiler/TigerParser.g4 .
|
|
515
|
-
# discard the first 5 lines of TigerParser
|
|
516
|
-
RUN tail -n +6 TigerParser.g4 &gt; TigerParser.g4
|
|
517
|
-
# combine the two files into one
|
|
518
|
-
RUN cat TigerLexer.g4 TigerParser.g4 &gt; Tiger.g4
|
|
519
|
-
# replace lexer grammar TigerLexer; with grammer Tiger;
|
|
520
|
-
RUN sed -i 's/lexer grammar TigerLexer;/grammar Tiger;/g' Tiger.g4
|
|
521
|
-
SAVE ARTIFACT Tiger.g4
|
|
522
|
-
|
|
523
|
-
# Runs my tests
|
|
524
|
-
test:
|
|
525
|
-
FROM +deps
|
|
526
|
-
COPY src/ src/
|
|
527
|
-
COPY lombok.config pom.xml .
|
|
528
|
-
RUN mvn test
|
|
529
|
-
</code></pre>
|
|
530
|
-
<p>To execute an Earthfile, all you need is the <a
|
|
531
|
-
href="https://earthly.dev/get-earthly">Earthly CLI</a> and Docker.
|
|
532
|
-
Everything else is containerized! For example, I wouldn't need Java, Maven, or Make to
|
|
533
|
-
build this project. If I wanted to set up CI with GitHub Actions, all I would need to do is
|
|
534
|
-
use the <a href="https://github.com/earthly/actions-setup">Earthly Setup
|
|
535
|
-
Action</a> and then run my target, e.g. <code>earthly
|
|
536
|
-
+test</code>.</p>
|
|
537
|
-
<h2>IDE</h2>
|
|
538
|
-
<p><a href="https://www.jetbrains.com/idea/">IntelliJ IDEA</a>
|
|
539
|
-
is objectively the best Java IDE. If you're comfortable with VS Code and you don't
|
|
540
|
-
plan to write Java in the future, then sticking with VS Code is okay. If you plan to do more
|
|
541
|
-
work in Java, then I would <em>highly</em> suggest IntelliJ.</p>
|
|
542
|
-
<p>Java has a reputation as a mediocre language. That may be true, but where it shines
|
|
543
|
-
is in the ecosystem. IntelliJ is a perfect example of this. It has incredibly powerful
|
|
544
|
-
analysis and refactoring capabilities, and very tightly integrates with Java tools like
|
|
545
|
-
Maven.</p>
|
|
546
|
-
<p>As an example, I was able to develop my ANTLR grammar using a <a
|
|
547
|
-
href="https://plugins.jetbrains.com/plugin/7358-antlr-v4">plugin</a>
|
|
548
|
-
which allowed easy testing.</p>
|
|
549
|
-
<p>As a student, you can get a copy of IntelliJ for free with <a
|
|
550
|
-
href="https://www.jetbrains.com/community/education/#students">JetBrain's
|
|
551
|
-
education program</a>.</p>
|
|
552
|
-
<h2>Libraries</h2>
|
|
553
|
-
<p>I use a ton of libraries because Java has <em>so</em> many great
|
|
554
|
-
libraries. Here's what I'm currently using in my project. Some of them
|
|
555
|
-
<em>might</em> be overkill, e.g. Log4J2.</p>
|
|
556
|
-
<ul>
|
|
557
|
-
<li><a href="https://junit.org/junit5/">JUnit 5</a>
|
|
558
|
-
<ul>
|
|
559
|
-
<li>So much better than the default JUnit 4. I'll show why in the testing
|
|
560
|
-
section.</li>
|
|
561
|
-
</ul>
|
|
562
|
-
</li>
|
|
563
|
-
<li><a href="https://projectlombok.org">Lombok</a>
|
|
564
|
-
<ul>
|
|
565
|
-
<li>Generate Java code at compile time. Java is a verbose language — Lombok makes it
|
|
566
|
-
better.</li>
|
|
567
|
-
</ul>
|
|
568
|
-
</li>
|
|
569
|
-
<li><a href="https://github.com/google/guava">Guava</a>
|
|
570
|
-
<ul>
|
|
571
|
-
<li>I haven't used this yet, but I often reach for some of its
|
|
572
|
-
utilities.</li>
|
|
573
|
-
</ul>
|
|
574
|
-
</li>
|
|
575
|
-
<li><a href="https://logging.apache.org/log4j/2.x/">Log4J2</a>
|
|
576
|
-
<ul>
|
|
577
|
-
<li>As mentioned above, I use this for logging.</li>
|
|
578
|
-
</ul>
|
|
579
|
-
</li>
|
|
580
|
-
<li><a href="http://immutables.github.io">Immutables</a>
|
|
581
|
-
<ul>
|
|
582
|
-
<li>Generates immutable objects. I prefer a more functional style of programming that
|
|
583
|
-
avoids mutation. I suspect this will come in handy in later phases of the project. I've
|
|
584
|
-
used this both at AWS and in the Distributed Systems course — in both cases, it was quite
|
|
585
|
-
helpful.</li>
|
|
586
|
-
</ul>
|
|
587
|
-
</li>
|
|
588
|
-
<li><a href="https://picocli.info">picocli</a>
|
|
589
|
-
<ul>
|
|
590
|
-
<li>For my commands. Super easy to use, although you could probably get away with the
|
|
591
|
-
native Java libraries.</li>
|
|
592
|
-
</ul>
|
|
593
|
-
</li>
|
|
594
|
-
</ul>
|
|
595
|
-
<h2>Testing</h2>
|
|
596
|
-
<p>Testing the most important aspect of software development. Do yourself a favor and
|
|
597
|
-
come up with an effective strategy to test your work now. Having a solid testing methodology
|
|
598
|
-
will allow you to quickly verify your work, and will give you confidence that your code
|
|
599
|
-
works if you ever go back and refactor.</p>
|
|
600
|
-
<p>This might sound like a lot of work for a school project, but I think this
|
|
601
|
-
investment is worth it considering we're building a compiler for the
|
|
602
|
-
semester.</p>
|
|
603
|
-
<p>JUnit is the de facto standard for testing Java. JUnit 4 is the most common version
|
|
604
|
-
today, but JUnit 5 has some brilliant features.</p>
|
|
605
|
-
<p>One example is parameterized testing. For example, I have tests that check all the
|
|
606
|
-
sample files given to us by the course staff. I use a parameterized test to run the same
|
|
607
|
-
test on each file. This is much better than having a separate test for each file, and easily
|
|
608
|
-
allows you to add test cases.</p>
|
|
609
|
-
<p>Here's what a test looks like:</p>
|
|
610
|
-
<pre><code>public static Stream&lt;String&gt; TestBadLexer() {
|
|
611
|
-
var files =
|
|
612
|
-
Paths.get("src/test/resources/official/bad/lexer").toFile().listFiles();
|
|
613
|
-
assert files != null;
|
|
614
|
-
return Arrays.stream(files).map(File::getAbsolutePath);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
@ParameterizedTest
|
|
618
|
-
@MethodSource()
|
|
619
|
-
public void TestBadLexer(String file) {
|
|
620
|
-
var compiler = new TigerCompiler();
|
|
621
|
-
assertThrows(LexerException.class, () -&gt; compiler.compile(file, false));
|
|
622
|
-
}
|
|
623
|
-
</code></pre>
|
|
624
|
-
<p>JUnit will use the static method named <code>TestBadLexer</code> that
|
|
625
|
-
supplies a list of filenames to the <code>TestBadLexer</code> as the first
|
|
626
|
-
argument. My test then checks that the appropriate error is thrown.</p>
|
|
627
|
-
<p>Another great strategy is <a
|
|
628
|
-
href="https://kentcdodds.com/blog/effective-snapshot-testing">snapshot
|
|
629
|
-
testing</a>. This allows you to save some test output to a file. On subsequent runs,
|
|
630
|
-
the test output is compared with the saved output. If the output is different, the test
|
|
631
|
-
fails.</p>
|
|
632
|
-
<p>If the test fails, you can visually inspect the different. If the change is
|
|
633
|
-
expected, you can update the snapshot. If the change is unexpected, you can investigate
|
|
634
|
-
further.</p>
|
|
635
|
-
<p>I use this for investigating the <code>.tokens</code> file I produce. I
|
|
636
|
-
chose to use the <a
|
|
637
|
-
href="https://github.com/origin-energy/java-snapshot-testing">java-snapshot-testing
|
|
638
|
-
library</a>. It's not as ergonomic as what I've experienced in other
|
|
639
|
-
languages like TypeScript and Go, but it does seem to work well.</p>
|
|
640
|
-
<p>Here's an example test:</p>
|
|
641
|
-
<pre><code>@Test
|
|
642
|
-
@SneakyThrows
|
|
643
|
-
public void TestHelloWorldTokens() {
|
|
644
|
-
Path tokensFile = Paths.get("src/test/resources/hello.tokens");
|
|
645
|
-
Files.deleteIfExists(tokensFile);
|
|
646
|
-
|
|
647
|
-
var compiler = new TigerCompiler();
|
|
648
|
-
var file = "src/test/resources/hello.tiger";
|
|
649
|
-
// the `true` argument tells the compiler to write the tokens to a file
|
|
650
|
-
compiler.compile(file, true);
|
|
651
|
-
|
|
652
|
-
assert Files.exists(tokensFile);
|
|
653
|
-
|
|
654
|
-
expect.toMatchSnapshot(Files.readString(tokensFile));
|
|
655
|
-
|
|
656
|
-
Files.delete(tokensFile);
|
|
657
|
-
}
|
|
658
|
-
</code></pre>
|
|
659
|
-
<p>It saves a file named <code>__snapshots__/MainTest.snap</code>. The
|
|
660
|
-
file looks like this:</p>
|
|
661
|
-
<pre><code>com.shepherdjerred.compiler.MainTest.TestHelloWorldTokens=[
|
|
662
|
-
&lt;PROGRAM, "program"&gt;
|
|
663
|
-
&lt;ID, "demo_print"&gt;
|
|
664
|
-
&lt;LET, "let"&gt;
|
|
665
|
-
[the rest of the file goes on]
|
|
666
|
-
</code></pre>
|
|
667
|
-
<p>Let's say that I introduce a bug and a token is missing. This is an example of
|
|
668
|
-
the error I would see:</p>
|
|
669
|
-
<pre><code>au.com.origin.snapshots.exceptions.SnapshotMatchException: Error(s)
|
|
670
|
-
matching snapshot(s) (1 failure)
|
|
671
|
-
|
|
672
|
-
Missing content at line 18:
|
|
673
|
-
["&lt;INT, "int"&gt;",
|
|
674
|
-
"&lt;COMMA, ","&gt;",
|
|
675
|
-
"&lt;ID, "z"&gt;"]
|
|
676
|
-
</code></pre>
|
|
677
|
-
<p>I can then investigate why the snapshot no longer matches, and update it if the
|
|
678
|
-
changes look correct.</p>
|
|
679
|
-
<p>Here's my full <a
|
|
680
|
-
href="https://gist.github.com/shepherdjerred/4b13eed30431a91d5ad887932ba923c9"><code>MainTest.java</code></a>
|
|
681
|
-
file so that you can see how I've set all of this up.</p>
|
|
682
|
-
<h2>Conclusion</h2>
|
|
683
|
-
<p>I hope this was some use to you, and that you're as excited as I am for this
|
|
684
|
-
semester. If you need any advice about project setup or have some suggestions, feel free to
|
|
685
|
-
reach out!</p>
|
|
686
|
-
</content:encoded>
|
|
687
|
-
</item>
|
|
688
|
-
<item>
|
|
689
|
-
<title>Screen Time</title>
|
|
690
|
-
<link>https://sjer.red/blog/2023/screen-time/</link>
|
|
691
|
-
<guid isPermaLink="true">https://sjer.red/blog/2023/screen-time/</guid>
|
|
692
|
-
<description>How I use my phone less</description>
|
|
693
|
-
<pubDate>Sun, 01 Oct 2023 00:00:00 GMT</pubDate>
|
|
694
|
-
<content:encoded><h2>Table of Contents</h2>
|
|
695
|
-
<h2>Introduction</h2>
|
|
696
|
-
<p>Like many, I once used my phone quite often. Too often.</p>
|
|
697
|
-
<p>At some point, my usage started feeling out of control and impulsive. I'd pull
|
|
698
|
-
out my phone in public, like waiting in line or walking past a stranger. I'd read
|
|
699
|
-
through Hacker News and The New York Times both when waking up and before bed. When I felt
|
|
700
|
-
bored, I'd find something to distract me. Sometimes, it would be Instagram Reels
|
|
701
|
-
(Instagram's version of TikTok), which would often absorb an hour of my day. At my
|
|
702
|
-
worst, I would compulsively open up random apps and check if there was anything new. Slack,
|
|
703
|
-
Discord, Email, whatever.</p>
|
|
704
|
-
<p>I view technology like phones and laptops as a tool -- something to help me out. I
|
|
705
|
-
don't want them to rule my life, even if computing is a significant part of my
|
|
706
|
-
day-to-day life. I wanted to make my interaction with my phone more intentional.</p>
|
|
707
|
-
<p>I don't think getting the raw number of hours down is super important, as long
|
|
708
|
-
as my interaction and relationship with my phone is intentional usage rather than
|
|
709
|
-
compulsive. I want to be more thoughtful about how I use my time and interact with others. I
|
|
710
|
-
want to replace shallow interaction on social media with more meaningful, intentional time
|
|
711
|
-
spent with those I care about.</p>
|
|
712
|
-
<p>In 2020 and 2021, my screen time increased to around 3 hours daily. At some point,
|
|
713
|
-
I decided that I wanted to use my phone less, so I started taking small steps towards that
|
|
714
|
-
goal. I wrote this to share what worked for me so others could take away some
|
|
715
|
-
ideas.</p>
|
|
716
|
-
<p>This post is centered around iPhones since that's what I have. That's
|
|
717
|
-
important to note since both platforms have different features and capabilities. Android has
|
|
718
|
-
similar features, so most steps should have loose equivalents.</p>
|
|
719
|
-
<p>Please also note that I took the below steps over about two years. It would be
|
|
720
|
-
<em>very</em> hard to do everything all at once. I've organized the actions
|
|
721
|
-
in order so that the most approachable steps are first and the more extreme ones
|
|
722
|
-
last.</p>
|
|
723
|
-
<h2>Keep your Phone Far</h2>
|
|
724
|
-
<p>I used to keep my phone right at my bedside at night.</p>
|
|
725
|
-
<p>Since my phone was by my bed, I would use it every day in the morning and at night.
|
|
726
|
-
This would easily eat up an hour of my day. During the day, I kept my phone on my desk or
|
|
727
|
-
somewhere near me. This meant it was effortless to quickly check things or be sucked in
|
|
728
|
-
because it was too easy to access.</p>
|
|
729
|
-
<p>At night, I moved my phone charger from my nightstand to another room in my house.
|
|
730
|
-
This meant that using my phone at night and in the morning would require much more effort.
|
|
731
|
-
And, as a bonus, it meant I had to get out of bed to turn off my phone alarm, making it much
|
|
732
|
-
less likely that I snooze the alarm and fall back asleep.</p>
|
|
733
|
-
<p>This step represents a general concept that'll be repeated, making using your
|
|
734
|
-
phone harder and requiring more thought. The more effort it takes, the less you will use it.
|
|
735
|
-
Ideally, at least for me, I'll only be using my phone when it's genuinely
|
|
736
|
-
beneficial for me.</p>
|
|
737
|
-
<h2>Track your Screen Time</h2>
|
|
738
|
-
<p>If you don't already have it enabled, you should turn on <a
|
|
739
|
-
href="https://support.apple.com/en-us/HT208982">Screen Time</a>. This
|
|
740
|
-
will allow you to track your phone usage and determine what works for you. Additionally, you
|
|
741
|
-
can set time limits for specific apps.</p>
|
|
742
|
-
<p>For example, if you use some apps more than you would like, you can give yourself a
|
|
743
|
-
daily time limit. When you reach your time limit, iOS will notify you. You'll be given
|
|
744
|
-
the choice to exit the app or override the time limit.</p>
|
|
745
|
-
<p>I would always override the limit, so I configured iOS to require a PIN to override
|
|
746
|
-
the time limit. I quickly adapted to just typing in the PIN from memory, so I eventually
|
|
747
|
-
used a random PIN that I didn't know and stored it somewhere. I kept my PIN in
|
|
748
|
-
1Password, although you could use Notes or scribble it on paper and put it in your phone
|
|
749
|
-
case.</p>
|
|
750
|
-
<h2>Disable Notifications</h2>
|
|
751
|
-
<p>Notifications are the antithesis of what technology should be. Your phone should
|
|
752
|
-
<em>not</em> be telling you when to look at it -- looking at your phone should
|
|
753
|
-
be <em>your</em> choice.</p>
|
|
754
|
-
<p>Sometimes, notifications are more helpful than harmful, like receiving phone calls
|
|
755
|
-
or text messages. I recommend turning off all notifications on your phone, including text
|
|
756
|
-
messages.</p>
|
|
757
|
-
<h2>Use a Worse Phone</h2>
|
|
758
|
-
<p>Phones are enticing, which makes you want to use them. By making your phone less
|
|
759
|
-
pleasant to use, you'll want to use it less. The easiest thing you can do is use an
|
|
760
|
-
older phone. Don't upgrade to the latest iPhone; keep the one you have now until it
|
|
761
|
-
breaks or no longer receives software updates.</p>
|
|
762
|
-
<p>Aside from that, you can <a
|
|
763
|
-
href="https://www.theverge.com/23637672/grayscale-iphone-android-pixel-samsung-galaxy-how-to">configure
|
|
764
|
-
your iPhone to be in grayscale</a>. I also enabled a shortcut to toggle between
|
|
765
|
-
grayscale and color whenever I triple-clicked the power button to properly view things like
|
|
766
|
-
photos sent to me.</p>
|
|
767
|
-
<p>It was incredible how much this helped me. I'd open up Facebook or Instagram
|
|
768
|
-
and wouldn't end up scrolling for 20-30 minutes.</p>
|
|
769
|
-
<p>Lastly, I configured my iPhone to be less bright. You can <a
|
|
770
|
-
href="https://osxdaily.com/2014/03/21/reduce-white-point-ios/">reduce the white
|
|
771
|
-
point</a> so that your screen is less bright, independently of the brightness slider.
|
|
772
|
-
This made the phone very hard to use outdoors, meaning I no longer would pull out my phone
|
|
773
|
-
when walking past strangers or doing some outdoor social activity.</p>
|
|
774
|
-
<h2>Delete your Apps</h2>
|
|
775
|
-
<p>Having an app on your phone for every service you use is easy. Things like social
|
|
776
|
-
media, email, finances. Many of these apps are pretty convenient to have. That convenience
|
|
777
|
-
makes it very easy to get sucked into those apps. Ask yourself if you
|
|
778
|
-
<em>really</em> need these apps on your phone or if you could rely on some other
|
|
779
|
-
device like your laptop.</p>
|
|
780
|
-
<p>For instance, I had a bunch of apps that I didn't need on my phone:</p>
|
|
781
|
-
<ul>
|
|
782
|
-
<li>Social Media (Instagram, Facebook, Twitter, Reddit, Discord)</li>
|
|
783
|
-
<li>Finance (Budgeting, Bank apps, Robinhood)</li>
|
|
784
|
-
<li>Email</li>
|
|
785
|
-
<li>Calendar</li>
|
|
786
|
-
<li>Messaging (Telegram, Facebook Messenger)</li>
|
|
787
|
-
</ul>
|
|
788
|
-
<p>I deleted all of the above and now rely on the web equivalents. You don't
|
|
789
|
-
necessarily need to delete everything all at once. Start with just a few apps and slowly
|
|
790
|
-
pare down what's on your phone.</p>
|
|
791
|
-
<p>Deleting messaging apps is probably the most challenging part of this. You'll
|
|
792
|
-
have to let your friends know that you no longer have that app on your phone and that you
|
|
793
|
-
should be reached through text (or your messaging app of choice) instead.</p>
|
|
794
|
-
<h2>Switch Browsers</h2>
|
|
795
|
-
<p>Eventually, I ended up without distracting apps on my phone, but unfortunately, I
|
|
796
|
-
still had a web browser. After deleting Facebook and Instagram, I started using the web
|
|
797
|
-
versions on my phone. I opened those sites way more often than I would've
|
|
798
|
-
liked.</p>
|
|
799
|
-
<p>iOS doesn't allow you to delete the Safari browser, meaning there's no
|
|
800
|
-
way to stop yourself from using the web on your iPhone. The next best thing is to use a web
|
|
801
|
-
browser that's harder to use. I switched my default browser from Safari to Firefox
|
|
802
|
-
Focus. Firefox Focus doesn't have the concept of tabs -- you can only have one site
|
|
803
|
-
open at a time. It also doesn't store history, bookmarks, cookies, or anything else,
|
|
804
|
-
meaning you must type in whatever site you want to go to and log in every time. This made it
|
|
805
|
-
much harder to use Instagram and Facebook, so I went there less often.</p>
|
|
806
|
-
<p>Of course, I still had Safari installed, so I could always open that up, but I
|
|
807
|
-
never found myself doing that, so I consider that a win.</p>
|
|
808
|
-
<h2>Delete Social Media Accounts</h2>
|
|
809
|
-
<p>This is hard, and I don't expect most to do it. For me, it made a lot of
|
|
810
|
-
sense. Even after deleting apps from my phone, I scrolled through Facebook, LinkedIn,
|
|
811
|
-
Reddit, etc. on my laptop. I didn't feel like I was getting much value from that
|
|
812
|
-
behavior, so I deleted my accounts.</p>
|
|
813
|
-
<p>Facebook is nice for staying in touch with others, especially those who live
|
|
814
|
-
further away or whom I don't talk to often. I decided that, for me, these connections
|
|
815
|
-
were not as meaningful. If I want to stay in contact with someone (or vice-versa), we will
|
|
816
|
-
find a way. People communicated before Facebook and other social media, so if we care about
|
|
817
|
-
each other, it will happen even if it does require more effort.</p>
|
|
818
|
-
<h2>Batch Consumption</h2>
|
|
819
|
-
<p>I'm still experimenting with what to do for news.</p>
|
|
820
|
-
<p>I didn't want to be disconnected from the world, so I subscribed to the New
|
|
821
|
-
York Times and read Hacker News frequently.</p>
|
|
822
|
-
<p>I generally read the New York Times website once or twice a day for the news. That
|
|
823
|
-
was a big time sink without any clear value added to my life. I decided to try reading a
|
|
824
|
-
physical newspaper by getting the Sunday edition delivered to my house. That reduced my
|
|
825
|
-
screen time, but it still wasn't adding value and was an even bigger time sink because
|
|
826
|
-
of how big the paper was. I would spend hours on Sundays reading about things that
|
|
827
|
-
didn't impact me.</p>
|
|
828
|
-
<p>My latest attempt is a subscription to <a
|
|
829
|
-
href="https://www.slow-journalism.com/">Delayed Gratification</a>, a
|
|
830
|
-
magazine published four times a year with news from the last quarter. Ideally, this means I
|
|
831
|
-
read the news only a few days each year, and the stories I read are of higher quality and
|
|
832
|
-
importance.</p>
|
|
833
|
-
<p>I've found a lot of value in Hacker News. I've learned so much from the
|
|
834
|
-
site; I could not give it up without a negative impact. At first, I enabled the
|
|
835
|
-
<code>noprocrast</code> feature, which did help, but I felt I could do better.
|
|
836
|
-
For a time, I used the <a href="https://hckrnews.com/">hckr news
|
|
837
|
-
site</a> to view only the most important stories every day. I was still checking that
|
|
838
|
-
site too much, so I moved over to <a
|
|
839
|
-
href="https://hackernewsletter.com/">Hacker Newsletter</a>.</p>
|
|
840
|
-
<h2>Conclusion</h2>
|
|
841
|
-
<p>With the steps above, over two years, I've reduced my daily screen time from
|
|
842
|
-
its peak of about 3 hours to about 15-20 minutes. This varies based on a few factors, like
|
|
843
|
-
my mental health. Generally, the more I use my phone, the worse I am. It indicates that
|
|
844
|
-
I'm bored or trying to fill time. There are some exceptions to this, like when I'm
|
|
845
|
-
intentionally reading some long-form piece on my phone, which I do from time to
|
|
846
|
-
time.</p>
|
|
847
|
-
<p>I hope these steps were helpful and can serve as a starting point for those looking
|
|
848
|
-
to have a more intentional relationship with technology. Please reach out to me if
|
|
849
|
-
you'd like to discuss anything!</p>
|
|
850
|
-
</content:encoded>
|
|
851
|
-
</item>
|
|
852
|
-
<item>
|
|
853
|
-
<title>Writing</title>
|
|
854
|
-
<link>https://sjer.red/blog/2023/writing/</link>
|
|
855
|
-
<guid isPermaLink="true">https://sjer.red/blog/2023/writing/</guid>
|
|
856
|
-
<description>If you can't write then you can't think.</description>
|
|
857
|
-
<pubDate>Thu, 28 Sep 2023 00:00:00 GMT</pubDate>
|
|
858
|
-
<content:encoded><p>Writing is hard. You're condensing <em>your</em>
|
|
859
|
-
thoughts into more general concepts that can (hopefully) be understood by others. It's
|
|
860
|
-
equivalent to compressing a giant file (your mind) into some smaller artifact (some
|
|
861
|
-
scribbles on a page).</p>
|
|
862
|
-
<p>Writing is valuable. It's how we can give the gift of knowledge to the future.
|
|
863
|
-
One can learn some concept and teach it to others with no investment other than the time
|
|
864
|
-
spent compressing that knowledge.</p>
|
|
865
|
-
<p>Writing is introspective and philosophical. Writing delves into the nature of
|
|
866
|
-
knowledge. What's worth sharing with others?</p>
|
|
867
|
-
<p>The process of writing is good practice for thinking. It forces you to focus on
|
|
868
|
-
communicating clearly. It makes you think about language and how to effectively convey
|
|
869
|
-
concepts. It makes you consider if your examples are truly clear and engaging, or just a
|
|
870
|
-
distraction not adding any value.</p>
|
|
871
|
-
<p>I'm really bad at writing. My thoughts are chaotic and disorganized and hard
|
|
872
|
-
to communicate. Praciting writing gives me the chance to hone what I've been thinking
|
|
873
|
-
about, and work "muscles" to help me communicate more clearly.</p>
|
|
874
|
-
</content:encoded>
|
|
875
|
-
</item>
|
|
876
|
-
<item>
|
|
877
|
-
<title>XState</title>
|
|
878
|
-
<link>https://sjer.red/blog/2023/xstate/</link>
|
|
879
|
-
<guid isPermaLink="true">https://sjer.red/blog/2023/xstate/</guid>
|
|
880
|
-
<description>State Machines reduce edge cases</description>
|
|
881
|
-
<pubDate>Thu, 14 Sep 2023 00:00:00 GMT</pubDate>
|
|
882
|
-
<content:encoded><p>import { Image } from "astro:assets";
|
|
883
|
-
import channel from "./xstate/channel.png";
|
|
884
|
-
import game from "./xstate/game.png";
|
|
885
|
-
import diagram from "./xstate/diagram.png";
|
|
886
|
-
import simulation from "./xstate/simulation.mp4";</p>
|
|
887
|
-
<p>My current side project, <a
|
|
888
|
-
href="https://github.com/shepherdjerred/discord-plays-pokemon">Discord Plays
|
|
889
|
-
Pokemon</a>, has a lot of dependencies. The application streams video with Discord,
|
|
890
|
-
which presents a challenge. Discord does not provide APIs for streaming video, and I
|
|
891
|
-
didn't want to have to reverse-engineer the client. I chose to automate interactions
|
|
892
|
-
with Discord's web application using Selenium, which has yielded great results. It can
|
|
893
|
-
programmatically stream video to a specific voice channel. This has worked very well so far
|
|
894
|
-
-- users are able to play real-time games of Pokemon with each other just using
|
|
895
|
-
Discord's text chat!</p>
|
|
896
|
-
<p>&lt;figure&gt;
|
|
897
|
-
&lt;Image src={channel} alt="A screenshot of Discord text chat with two users
|
|
898
|
-
inputting commands" height="100px" /&gt;
|
|
899
|
-
&lt;figcaption&gt;
|
|
900
|
-
Two users can input commands at the same time. <code>D</code> means to simulate
|
|
901
|
-
a down-button press once, and <code>10r</code> means to
|
|
902
|
-
simulate a right-button press ten times.</p>
|
|
903
|
-
<pre><code>If the command is valid, the bot will react to the message with a 👍
|
|
904
|
-
once
|
|
905
|
-
the command is applied to the game.
|
|
906
|
-
</code></pre>
|
|
907
|
-
<p>&lt;/figcaption&gt;
|
|
908
|
-
&lt;/figure&gt;</p>
|
|
909
|
-
<p>I brute-forced a lot of this code to get the bot working quickly. While it works
|
|
910
|
-
well, adding new features was not easy. I wanted only to have the bot stream if a user was
|
|
911
|
-
in the voice chat. This requires tracking the state of the bot. Was the bot able to log in
|
|
912
|
-
to Discord? Is the bot streaming or not? Has there been any error? How do I switch between
|
|
913
|
-
browser tabs since there is one tab for Discord and another for the <a
|
|
914
|
-
href="https://www.emulatorjs.com/">browser-based emulator</a>?</p>
|
|
915
|
-
<p>Switching between tabs was an easy problem to fix. I created two instances of
|
|
916
|
-
Firefox -- one for the stream and another for the video. This eliminated a whole class of
|
|
917
|
-
errors at a slight performance cost.</p>
|
|
918
|
-
<p>&lt;figure&gt;
|
|
919
|
-
&lt;Image src={game} alt="A screenshot of Discord streaming Pokemon" /&gt;
|
|
920
|
-
&lt;figcaption&gt;Video is streamed in real-time with instant feedback for the
|
|
921
|
-
applied inputs.&lt;/figcaption&gt;
|
|
922
|
-
&lt;/figure&gt;</p>
|
|
923
|
-
<p>Tracking the state, however, was not something I wanted to do. There are a lot of
|
|
924
|
-
subtle edge cases that I didn't want to deal with. I felt state machines would be
|
|
925
|
-
applicable, but I had never used them in TypeScript.</p>
|
|
926
|
-
<p>I found the <a href="https://xstate.js.org/">XState</a>
|
|
927
|
-
project and immediately fell in love. The project is incredibly polished and has excellent
|
|
928
|
-
support for VS Code and TypeScript. I ported over my old code to a state machine, although
|
|
929
|
-
understanding the concepts that XState introduced took some time.</p>
|
|
930
|
-
<p>&lt;figure&gt;
|
|
931
|
-
&lt;Image
|
|
932
|
-
src={diagram}
|
|
933
|
-
alt="A screenshot of VS Code with a code pane to the left and a state machine diagram
|
|
934
|
-
to the right."
|
|
935
|
-
/&gt;
|
|
936
|
-
&lt;figcaption&gt;XState integrates well with VS Code.&lt;/figcaption&gt;
|
|
937
|
-
&lt;/figure&gt;</p>
|
|
938
|
-
<p>I was surprised at how helpful the XState VS Code plugin was. It allows me to see a
|
|
939
|
-
diagram of my state machine. You can simulate state transitions to understand what your
|
|
940
|
-
state machine will do.</p>
|
|
941
|
-
<p>&lt;figure&gt;
|
|
942
|
-
&lt;video controls&gt;
|
|
943
|
-
&lt;source src={simulation} type="video/mp4" /&gt;
|
|
944
|
-
&lt;/video&gt;
|
|
945
|
-
&lt;figcaption&gt;The XState VS Code extension lets you step through your state
|
|
946
|
-
transitions.&lt;/figcaption&gt;
|
|
947
|
-
&lt;/figure&gt;</p>
|
|
948
|
-
<p>Aside from the coolness of the extension, the library itself is quite polished and
|
|
949
|
-
well-documented. Porting over my old code was simple because of how well XState integrates
|
|
950
|
-
with promises.</p>
|
|
951
|
-
<p>Here's an example of the state machine's state for starting a Discord
|
|
952
|
-
video stream. The method in <code>src</code> is invoked when the
|
|
953
|
-
<code>starting_stream</code> state is reached. Once the promise is complete,
|
|
954
|
-
<code>onDone</code> is called, which transitions the machine to the
|
|
955
|
-
<code>streaming</code> state.</p>
|
|
956
|
-
<p>&lt;figure&gt;</p>
|
|
957
|
-
<pre><code>// @noErrors
|
|
958
|
-
starting_stream: {
|
|
959
|
-
invoke: {
|
|
960
|
-
src: async ({ driver }, _event) =&gt; {
|
|
961
|
-
await joinVoiceChat(driver);
|
|
962
|
-
return await shareScreen(driver);
|
|
963
|
-
},
|
|
964
|
-
onDone: {
|
|
965
|
-
target: "streaming",
|
|
966
|
-
},
|
|
967
|
-
onError: {
|
|
968
|
-
target: "is_error",
|
|
969
|
-
actions: (_context, event) =&gt; {
|
|
970
|
-
console.error(event);
|
|
971
|
-
},
|
|
972
|
-
},
|
|
973
|
-
},
|
|
974
|
-
},
|
|
975
|
-
</code></pre>
|
|
976
|
-
<p>&lt;figcaption&gt;The state for starting a Discord
|
|
977
|
-
stream.&lt;/figcaption&gt;
|
|
978
|
-
&lt;/figure&gt;</p>
|
|
979
|
-
<p>I even wrote some quick unit tests to ensure it works properly. This test was much
|
|
980
|
-
easier to write than tests without a state machine.</p>
|
|
981
|
-
<p>&lt;figure&gt;</p>
|
|
982
|
-
<pre><code>// @noErrors
|
|
983
|
-
test("able to reach the streaming state", (done) =&gt; {
|
|
984
|
-
const actor = interpret(streamMachine)
|
|
985
|
-
.onTransition((state) =&gt; {
|
|
986
|
-
if (state.matches("is_ready")) {
|
|
987
|
-
actor.send({ type: "start_stream" });
|
|
988
|
-
}
|
|
989
|
-
if (state.matches("is_streaming")) {
|
|
990
|
-
done();
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
|
-
actor.start();
|
|
994
|
-
});
|
|
995
|
-
</code></pre>
|
|
996
|
-
<p>&lt;figcaption&gt;Unit testing a state machine is straightforward. This
|
|
997
|
-
would've been a <em>lot</em> more code without
|
|
998
|
-
XState!&lt;/figcaption&gt;
|
|
999
|
-
&lt;/figure&gt;</p>
|
|
1000
|
-
<p>Hooking the entire thing up to the application wasn't hard, either. This
|
|
1001
|
-
allows the bot to enter the voice channel and stream only when people are in the
|
|
1002
|
-
channel.</p>
|
|
1003
|
-
<p>&lt;figure&gt;</p>
|
|
1004
|
-
<pre><code>// @noErrors
|
|
1005
|
-
const stream = interpret(streamMachine);
|
|
1006
|
-
|
|
1007
|
-
stream.start();
|
|
1008
|
-
|
|
1009
|
-
handleChannelUpdate(async (channel_count) =&gt; {
|
|
1010
|
-
if (channel_count &gt; 0) {
|
|
1011
|
-
stream.send({ type: "start_stream" });
|
|
1012
|
-
} else {
|
|
1013
|
-
stream.send({ type: "end_stream" });
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
</code></pre>
|
|
1018
|
-
<p>&lt;figcaption&gt;A complex set of interactions become so
|
|
1019
|
-
easy.&lt;/figcaption&gt;
|
|
1020
|
-
&lt;/figure&gt;</p>
|
|
1021
|
-
<p>Overall, using XState feels like a huge win. I can be more confident about how I
|
|
1022
|
-
interact with Selenium and Discord. I hope to move more of my application to XState, which
|
|
1023
|
-
will significantly help when implementing new input methods and notification
|
|
1024
|
-
systems.</p>
|
|
1025
|
-
</content:encoded>
|
|
1026
|
-
</item>
|
|
1027
|
-
<item>
|
|
1028
|
-
<title>Astro</title>
|
|
1029
|
-
<link>https://sjer.red/blog/2023/astro/</link>
|
|
1030
|
-
<guid isPermaLink="true">https://sjer.red/blog/2023/astro/</guid>
|
|
1031
|
-
<description>Astro brings great tooling to static HTML</description>
|
|
1032
|
-
<pubDate>Sun, 10 Sep 2023 00:00:00 GMT</pubDate>
|
|
1033
|
-
<content:encoded><p>It's incredibly easy to find poorly made single-page
|
|
1034
|
-
applications (SPA) today. These applications break user expectations by not acting like
|
|
1035
|
-
standard webpages. The page title doesn't update on navigation, the back/forward
|
|
1036
|
-
buttons don't work, the scroll position is not saved, and refreshing the page causes
|
|
1037
|
-
surprising behavior.</p>
|
|
1038
|
-
<p>A particularly bad site might be poorly optimized and take a while to load on a
|
|
1039
|
-
slow connection or perform poorly when rendered client-side.</p>
|
|
1040
|
-
<p>SPAs tend to be written with libraries like React and Vue. They are great for
|
|
1041
|
-
developer productivity, and I find them a joy to use. What I don't like is the amount
|
|
1042
|
-
of work that goes into making them feel like first-class citizens of the web. You have the
|
|
1043
|
-
handle the above, and so much more! What about SEO or users that disable
|
|
1044
|
-
JavaScript?</p>
|
|
1045
|
-
<p>Many sites do need the interactivity that these libraries provide, but there are
|
|
1046
|
-
plenty that don't. Content-heavy sites, like this one, are mostly read and have little
|
|
1047
|
-
client-side interaction that would require JavaScript. You could write the site with plain
|
|
1048
|
-
HTML/CSS, but you lose the excellent tooling that React provides. Alternatives include
|
|
1049
|
-
server-side rendering with a host like Vercel or static site generation with heavy software
|
|
1050
|
-
like Gatsby.</p>
|
|
1051
|
-
<p>I was determined not to compromise. I didn't want to buy into Vercel and
|
|
1052
|
-
certainly didn't want a super complicated build process with a bunch of
|
|
1053
|
-
buy-in.</p>
|
|
1054
|
-
<p>Enter <a href="https://astro.build/">Astro</a>.</p>
|
|
1055
|
-
<p>It's a perfect fit. By default, it outputs sites with zero JavaScript. Using
|
|
1056
|
-
familiar React-ish syntax, you can pass props or call JS at compile time in your components.
|
|
1057
|
-
You get the development speed up while also providing sites that perform great and respect
|
|
1058
|
-
your users.</p>
|
|
1059
|
-
<p>The feature list is jam-packed, and everything seems to work. The documentation is
|
|
1060
|
-
thorough, and it is a joy to use.</p>
|
|
1061
|
-
<p>I migrated this blog over to Astro in a couple of days, and I was able to add a
|
|
1062
|
-
bunch of new functionality with little effort, such as generated Open Graph images and an
|
|
1063
|
-
RSS feed that updates automatically.</p>
|
|
1064
|
-
<p>The site now has zero client-side JavaScript and does not need a server beyond
|
|
1065
|
-
static file hosting. The code is pretty similar to what it was before.</p>
|
|
1066
|
-
<p>I would highly recommend checking out <a
|
|
1067
|
-
href="https://astro.build/">Astro</a>. I'm only scratching the
|
|
1068
|
-
surface of what you can do with it.</p>
|
|
1069
|
-
</content:encoded>
|
|
1070
|
-
</item>
|
|
1071
|
-
<item>
|
|
1072
|
-
<title>Software Testing</title>
|
|
1073
|
-
<link>https://sjer.red/blog/2023/software-testing/</link>
|
|
1074
|
-
<guid isPermaLink="true">https://sjer.red/blog/2023/software-testing/</guid>
|
|
1075
|
-
<description>Integration tests deserve the most attention</description>
|
|
1076
|
-
<pubDate>Fri, 18 Aug 2023 00:00:00 GMT</pubDate>
|
|
1077
|
-
<content:encoded><p>You should verify every desired behavior of your project.</p>
|
|
1078
|
-
<p>Tests exist to verify some behavior of the object being tested. Some tests can be
|
|
1079
|
-
manual, such as manually executing a program and verifying that the program works as
|
|
1080
|
-
you'd expect. When performing manual testing, the project requirements live in your
|
|
1081
|
-
head and aren't particularly durable. If you return to a project months later, you
|
|
1082
|
-
might not know the desired behavior. Additionally, manual testing requires valuable time and
|
|
1083
|
-
effort to perform. Manual testing does not scale. As you add more functionality, you will
|
|
1084
|
-
need more time, discipline, and the ability to test your project's behavior
|
|
1085
|
-
manually.</p>
|
|
1086
|
-
<p>Automated tests help by explicitly defining the expected behavior, and it provides
|
|
1087
|
-
a way to run tests with near-zero manual effort. If the automated tests are easy to run,
|
|
1088
|
-
then you can quickly make a change, run the tests, and verify that your change worked as
|
|
1089
|
-
expected. If your automated tests have low coverage, you will have less confidence and
|
|
1090
|
-
resort to manual testing.</p>
|
|
1091
|
-
<p>Your tests will essentially become the "source of truth" for the expected
|
|
1092
|
-
behavior of your project.</p>
|
|
1093
|
-
<p>There are many forms of automated testing, to name a few: unit, integration, and
|
|
1094
|
-
end-to-end.</p>
|
|
1095
|
-
<p>What kind of tests you should write largely depends on what type of software
|
|
1096
|
-
you're testing. The classic testing pyramid would suggest that you write many unit
|
|
1097
|
-
tests. I think that this is entirely wrong.</p>
|
|
1098
|
-
<blockquote>
|
|
1099
|
-
<p>Unit tests can be great if the project is well-architected and if the project is in
|
|
1100
|
-
a language that lends itself well to unit testing. I've had a great experience with
|
|
1101
|
-
unit testing in Java and absolutely terrible experiences with JavaScript.</p>
|
|
1102
|
-
</blockquote>
|
|
1103
|
-
<p>Testing is essential, but that doesn't mean you should dump unlimited time
|
|
1104
|
-
into it. You'll catch the most bugs by running tests in an environment that is as close
|
|
1105
|
-
to production as possible. These are usually called integration tests or end-to-end
|
|
1106
|
-
tests.</p>
|
|
1107
|
-
<p>The tradeoff is that being "closer to production" usually means
|
|
1108
|
-
"really hard or slow to run".</p>
|
|
1109
|
-
<p>So, what kind of tests should you write?</p>
|
|
1110
|
-
<p>If the primary purpose of your project is to provide an API, you should write tests
|
|
1111
|
-
that check that your API contract is followed to the letter. You might want to have
|
|
1112
|
-
performance tests to ensure that it can have the desired response time.</p>
|
|
1113
|
-
<p>If you're writing a software library, you'll want many unit tests to
|
|
1114
|
-
verify the behavior of the methods you expose. You might want some integration tests, but
|
|
1115
|
-
you might be able to get away without any.</p>
|
|
1116
|
-
<p>For web applications, you should skip unit tests. Your application depends on the
|
|
1117
|
-
browser, so do your testing in a <em>real</em> browser. Communicate with
|
|
1118
|
-
<em>real</em> APIs and not mocks. You might want unit tests only for parts of
|
|
1119
|
-
your application that are not dependent on the browser or particularly tricky
|
|
1120
|
-
behavior.</p>
|
|
1121
|
-
</content:encoded>
|
|
1122
|
-
</item>
|
|
1123
|
-
<item>
|
|
1124
|
-
<title>Debugging C in VS Code</title>
|
|
1125
|
-
<link>https://sjer.red/blog/2022/c-debugging-vscode/</link>
|
|
1126
|
-
<guid isPermaLink="true">https://sjer.red/blog/2022/c-debugging-vscode/</guid>
|
|
1127
|
-
<description>Using VS Code to debug C is pretty easy</description>
|
|
1128
|
-
<pubDate>Sun, 23 Oct 2022 00:00:00 GMT</pubDate>
|
|
1129
|
-
<content:encoded><p>Knowing how to use a debugger for the tools that you use is one of
|
|
1130
|
-
the best investments you can make.</p>
|
|
1131
|
-
<p>Debuggers help you explore the state of your program. They provide feedback much
|
|
1132
|
-
faster than other debugging methods, like print statements. They also allow you to see
|
|
1133
|
-
<em>everything</em>, whereas a print statement will only show what you choose to
|
|
1134
|
-
print. This can be very helpful when part of a program that you expect to be working
|
|
1135
|
-
correctly is actually misbehaving.</p>
|
|
1136
|
-
<p>Advanced Operating Systems, the course that I'm currently taking for my
|
|
1137
|
-
Masters are Georgia Tech, requires that you spend a fair bit of time working in C. C is a
|
|
1138
|
-
fine language, but it is very cumbersome to debug programs without a debugger. Save yourself
|
|
1139
|
-
some frustration and set your debugging environment up before you start working on your
|
|
1140
|
-
program.</p>
|
|
1141
|
-
<p>VS Code makes this very easy.</p>
|
|
1142
|
-
<h2>Setup</h2>
|
|
1143
|
-
<ol>
|
|
1144
|
-
<li>
|
|
1145
|
-
<p>Install the <a
|
|
1146
|
-
href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools">C/C++
|
|
1147
|
-
extension</a></p>
|
|
1148
|
-
</li>
|
|
1149
|
-
<li>
|
|
1150
|
-
<p>Open up the "Run and Debug" pane</p>
|
|
1151
|
-
</li>
|
|
1152
|
-
<li>
|
|
1153
|
-
<p>Click the cog icon to open up the launch.json file which contains your debugging
|
|
1154
|
-
configuration</p>
|
|
1155
|
-
</li>
|
|
1156
|
-
<li>
|
|
1157
|
-
<p>Copy this into the launch.json file:</p>
|
|
1158
|
-
<pre><code>{
|
|
1159
|
-
"name": "debug",
|
|
1160
|
-
"type": "cppdbg",
|
|
1161
|
-
"request": "launch",
|
|
1162
|
-
"program": "${workspaceFolder}/&lt;path to your compiled
|
|
1163
|
-
binary&gt;",
|
|
1164
|
-
"args": ["&lt;your program arguments&gt;"],
|
|
1165
|
-
"stopAtEntry": false,
|
|
1166
|
-
"cwd": "${fileDirname}",
|
|
1167
|
-
"environment": [],
|
|
1168
|
-
"externalConsole": false,
|
|
1169
|
-
"MIMode": "gdb",
|
|
1170
|
-
"setupCommands": [
|
|
1171
|
-
{
|
|
1172
|
-
"description": "Enable pretty-printing for gdb",
|
|
1173
|
-
"text": "-enable-pretty-printing",
|
|
1174
|
-
"ignoreFailures": true
|
|
1175
|
-
},
|
|
1176
|
-
{
|
|
1177
|
-
"description": "Set Disassembly Flavor to Intel",
|
|
1178
|
-
"text": "-gdb-set disassembly-flavor intel",
|
|
1179
|
-
"ignoreFailures": true
|
|
1180
|
-
}
|
|
1181
|
-
]
|
|
1182
|
-
}
|
|
1183
|
-
</code></pre>
|
|
1184
|
-
</li>
|
|
1185
|
-
<li>
|
|
1186
|
-
<p>Save the file and click on "Start Debugging"</p>
|
|
1187
|
-
</li>
|
|
1188
|
-
</ol>
|
|
1189
|
-
<p>VS Code will launch your binary and attach a debuggger. You can do all of the usual
|
|
1190
|
-
debugger things like set breakpoints and inspect program state.</p>
|
|
1191
|
-
</content:encoded>
|
|
1192
|
-
</item>
|
|
1193
|
-
<item>
|
|
1194
|
-
<title>Pointer Math in C</title>
|
|
1195
|
-
<link>https://sjer.red/blog/2022/c-pointer-math/</link>
|
|
1196
|
-
<guid isPermaLink="true">https://sjer.red/blog/2022/c-pointer-math/</guid>
|
|
1197
|
-
<description>2D arrays in C can be tricky</description>
|
|
1198
|
-
<pubDate>Sat, 15 Oct 2022 00:00:00 GMT</pubDate>
|
|
1199
|
-
<content:encoded><p>C is a very confusing language.</p>
|
|
1200
|
-
<p>The world is built on C, and some people are able to get <em>very</em>
|
|
1201
|
-
good at it, but I am not one of those people.</p>
|
|
1202
|
-
<p>I pick it up for class, learn it, use it, appreciate it, and then forget it. Why
|
|
1203
|
-
would I want to write an application in C? I'm not a systems programmer, so I
|
|
1204
|
-
don't touch it.</p>
|
|
1205
|
-
<p>One side effect of this usage pattern is that I quickly forget how pointers and
|
|
1206
|
-
manual memory management work.</p>
|
|
1207
|
-
<p>Pointers are the semantics of <code>calloc</code> and
|
|
1208
|
-
<code>free</code> are easy enough to refresh on. It takes me a little while to
|
|
1209
|
-
remember if I need to use <code>*</code>, <code>&amp;</code>,
|
|
1210
|
-
<code>-&gt;</code>, or <code>.</code> when working with
|
|
1211
|
-
pointers. That's not a big deal though -- again, I can refresh myself on the syntax
|
|
1212
|
-
rather easily.</p>
|
|
1213
|
-
<p>One thing that does trip me up (and led to a very annoying bug in a barrier
|
|
1214
|
-
algorithm) is 2D arrays, or rather representing a 2D array as a pointer.</p>
|
|
1215
|
-
<p>Here's what I did.</p>
|
|
1216
|
-
<pre><code>int x;
|
|
1217
|
-
int y;
|
|
1218
|
-
int *array;
|
|
1219
|
-
|
|
1220
|
-
array = calloc(x * y, sizeof(int))
|
|
1221
|
-
|
|
1222
|
-
for (int i = 0; i &lt; x; i++) {
|
|
1223
|
-
for (int j = 0; j &lt; y; j++&gt;) {
|
|
1224
|
-
item = array[i + j];
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
</code></pre>
|
|
1228
|
-
<p>Now, this seems somewhat reasonable at first. The problem is that there is going to
|
|
1229
|
-
be a collision. <code>x = 0, y = 1</code> and <code>x = 1, y =
|
|
1230
|
-
0</code> will refer to the same slots in the array, which shouldn't
|
|
1231
|
-
happen!</p>
|
|
1232
|
-
<p>My next attempted was to change the array access to <code>array[i *
|
|
1233
|
-
j]</code>. This also doesn't work. Consider when <code>i = 0</code>
|
|
1234
|
-
or <code>j = 0</code>. Any multiplication by zero is zero, so these will all
|
|
1235
|
-
refer to the same slot.</p>
|
|
1236
|
-
<p>The correct solution is rather simple. The access should be <code>array[(i *
|
|
1237
|
-
y) + j]</code>. Let's prove this with an example.</p>
|
|
1238
|
-
<p>With <code>x = 2</code> and <code>y = 3</code>. These are
|
|
1239
|
-
the possible values of <code>i</code> and <code>j</code>:</p>
|
|
1240
|
-
<pre><code>i = 0, j = 0
|
|
1241
|
-
i = 0, j = 1
|
|
1242
|
-
i = 0, j = 2
|
|
1243
|
-
i = 1, j = 0
|
|
1244
|
-
i = 1, j = 1
|
|
1245
|
-
i = 1, j = 2
|
|
1246
|
-
</code></pre>
|
|
1247
|
-
<p>Now, lets see which slot in the array each pair will fit into.</p>
|
|
1248
|
-
<pre><code>i = 0, j = 0; (0 * 3) + 0 = 0
|
|
1249
|
-
i = 0, j = 1; (0 * 3) + 1 = 1
|
|
1250
|
-
i = 0, j = 2; (0 * 3) + 2 = 2
|
|
1251
|
-
i = 1, j = 0; (1 * 3) + 0 = 3
|
|
1252
|
-
i = 1, j = 1; (1 * 3) + 1 = 4
|
|
1253
|
-
i = 1, j = 2; (1 * 3) + 2 = 5
|
|
1254
|
-
</code></pre>
|
|
1255
|
-
<p>A unique index for each item! This is exactly what we wanted.</p>
|
|
1256
|
-
<p>You should perform these accesses based on the usage patterns of your data. The
|
|
1257
|
-
example above uses the x value as the column and the y value as the row. Accesses to
|
|
1258
|
-
sequential x values will be faster than accesses to sequential y values because of
|
|
1259
|
-
locality.</p>
|
|
1260
|
-
</content:encoded>
|
|
1261
|
-
</item>
|
|
1262
|
-
<item>
|
|
1263
|
-
<title>My struggles with C and libvirt</title>
|
|
1264
|
-
<link>https://sjer.red/blog/2022/libvirt/</link>
|
|
1265
|
-
<guid isPermaLink="true">https://sjer.red/blog/2022/libvirt/</guid>
|
|
1266
|
-
<description>Libvirt is pretty unintuitive</description>
|
|
1267
|
-
<pubDate>Thu, 06 Oct 2022 00:00:00 GMT</pubDate>
|
|
1268
|
-
<content:encoded><p>I recently completed a project for my Advanced Operating Systems
|
|
1269
|
-
course at Georgia Tech. I found that the project was hard not because of the problem itself.
|
|
1270
|
-
It's hard because it must be done in C, using <a
|
|
1271
|
-
href="https://libvirt.org/">libvirt</a> APIs which are poorly documented,
|
|
1272
|
-
using a test setup that is brittle.</p>
|
|
1273
|
-
<p>This article will hopefully save others some pain.</p>
|
|
1274
|
-
<p>I'll be providing examples of how to call relevant libvirt APIs, and some
|
|
1275
|
-
other useful information</p>
|
|
1276
|
-
<h2>General Tips</h2>
|
|
1277
|
-
<h3>Set up warnings</h3>
|
|
1278
|
-
<p>Before you do anything, enable every single warning that you can for the C
|
|
1279
|
-
compiler. The compiler can catch so many mistakes for you if you tell it to. This includes
|
|
1280
|
-
issues like implicit casting, incorrect printf calls, ineffectual assignments, and so much
|
|
1281
|
-
more.</p>
|
|
1282
|
-
<p>Update your Makefile to enable some warnings, for example:</p>
|
|
1283
|
-
<pre><code>GCCFLAGS += -Wall -Wextra -Wpedantic \
|
|
1284
|
-
-Wformat=2 -Wno-unused-parameter -Wshadow \
|
|
1285
|
-
-Wwrite-strings -Wstrict-prototypes -Wold-style-definition \
|
|
1286
|
-
-Wredundant-decls -Wnested-externs -Wmissing-include-dirs \
|
|
1287
|
-
-Wjump-misses-init -Wlogical-op -std=c11 \
|
|
1288
|
-
-Wstrict-overflow -fno-strict-aliasing \
|
|
1289
|
-
-Wconversion
|
|
1290
|
-
|
|
1291
|
-
compile:
|
|
1292
|
-
gcc -g vcpu_scheduler.c -o vcpu_scheduler -lvirt -lm $(GCCFLAGS)
|
|
1293
|
-
</code></pre>
|
|
1294
|
-
<h3>Refresh on C</h3>
|
|
1295
|
-
<p>I hadn't written C single my senior year of college. Like many, I've been
|
|
1296
|
-
spoiled by high-level languages that give you modern luxeries like lists and generics. I
|
|
1297
|
-
found that spending some time reading up on modern C and pointers both helped me
|
|
1298
|
-
out.</p>
|
|
1299
|
-
<p>I found these resources useful:</p>
|
|
1300
|
-
<ul>
|
|
1301
|
-
<li><a href="https://matt.sh/howto-c">How to C</a></li>
|
|
1302
|
-
</ul>
|
|
1303
|
-
<h3>Development Environment</h3>
|
|
1304
|
-
<p>The entire class will be scrambling to setup their development environment for the
|
|
1305
|
-
first week or so. I personally installed Linux on my desktop and used that to work with
|
|
1306
|
-
<a href="https://code.visualstudio.com/docs/remote/ssh">VS Code SSH
|
|
1307
|
-
Remote</a>, but there are plenty of other valid ways to work.</p>
|
|
1308
|
-
<h2>C/libvirt patterns</h2>
|
|
1309
|
-
<p>Now, onto some useful patterns.</p>
|
|
1310
|
-
<h2>Counting Host CPUs</h2>
|
|
1311
|
-
<p>C heavily uses output variables. Methods will often return ints that represent
|
|
1312
|
-
either a status code (e.g. was there an error or not), or the number of records returned, or
|
|
1313
|
-
both.</p>
|
|
1314
|
-
<p>This method returns 0 on success, and -1 on failure.</p>
|
|
1315
|
-
<p>It's always good to check return codes. Nothing nothing nothing is more
|
|
1316
|
-
frustrating than being confused about why straightforward code is failing -- this is one way
|
|
1317
|
-
to prevent that from happening.</p>
|
|
1318
|
-
<pre><code>virNodeInfo node_info;
|
|
1319
|
-
if (virNodeGetInfo(conn, &amp;node_info) == -1) {
|
|
1320
|
-
exit(1);
|
|
1321
|
-
}
|
|
1322
|
-
unsigned int num_cpus = node_info.cpus;
|
|
1323
|
-
</code></pre>
|
|
1324
|
-
<h2>Listing domains</h2>
|
|
1325
|
-
<p>This method takes a pointer. It will allocate a list of domains at the pointer, and
|
|
1326
|
-
the return value contains the number of items returned. You can iterate over the items using
|
|
1327
|
-
this information.</p>
|
|
1328
|
-
<p>Also, this method requires a bit of cleanup. You didn't dynamically allocate
|
|
1329
|
-
any memory, but the method you called did. Clean up so that you don't leak
|
|
1330
|
-
memory.</p>
|
|
1331
|
-
<pre><code>virDomainPtr *domain_list;
|
|
1332
|
-
int num_domains = virConnectListAllDomains(conn, &amp;domain_list, 0);
|
|
1333
|
-
// don't forget to cleanup!
|
|
1334
|
-
for (int i_domain = 0; i_domain &lt; num_domains; i_domain++) {
|
|
1335
|
-
virDomainFree(domain_list[i_domain]);
|
|
1336
|
-
}
|
|
1337
|
-
free(domain_list);
|
|
1338
|
-
</code></pre>
|
|
1339
|
-
<h2>Pin a vCPU to a pCPU</h2>
|
|
1340
|
-
<p>This one is rough.</p>
|
|
1341
|
-
<p>I think you can form the CPU map manually, but I just call libvirt and let it form
|
|
1342
|
-
it for me, then I pin CPUs as I like.</p>
|
|
1343
|
-
<pre><code>unsigned char *map;
|
|
1344
|
-
if (virNodeGetCPUMap(conn, &amp;map, NULL, 0) == -1) {
|
|
1345
|
-
exit(1);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// Use CPU 0
|
|
1349
|
-
VIR_USE_CPU(map, 0);
|
|
1350
|
-
// Don't use CPU 1
|
|
1351
|
-
VIR_UNUSE_CPU(map, 1);
|
|
1352
|
-
|
|
1353
|
-
int maplen = VIR_CPU_MAPLEN(number_of_physical_cpus);
|
|
1354
|
-
|
|
1355
|
-
// apply these settings to vcpu #0 in the given domain
|
|
1356
|
-
virDomainPinVcpu(domain, 0, map, maplen);
|
|
1357
|
-
free(map);
|
|
1358
|
-
</code></pre>
|
|
1359
|
-
</content:encoded>
|
|
1360
|
-
</item>
|
|
1361
|
-
<item>
|
|
1362
|
-
<title>Language Doesn't Matter</title>
|
|
1363
|
-
<link>https://sjer.red/blog/2022/language-doesnt-matter/</link>
|
|
1364
|
-
<guid isPermaLink="true">https://sjer.red/blog/2022/language-doesnt-matter/</guid>
|
|
1365
|
-
<description>It doesn't matter what programming language you learn first</description>
|
|
1366
|
-
<pubDate>Sun, 01 May 2022 00:00:00 GMT</pubDate>
|
|
1367
|
-
<content:encoded><p>Programming is rife with opinions. Everyone has a favorite IDE,
|
|
1368
|
-
framework, paradigm, test framework, and programming language. Engineers are very vocal
|
|
1369
|
-
about their preferences and often leave no room for a difference in opinion or even the idea
|
|
1370
|
-
that a contender may serve as a reasonable replacement for their favored choice. Before
|
|
1371
|
-
someone begins programming they have a myriad of choices to make before starting. The
|
|
1372
|
-
biggest of those choices might be what language to learn.</p>
|
|
1373
|
-
<p>Language makes an impact on how one thinks about problems and their solutions. Any
|
|
1374
|
-
language is capable of expressing the same idea in multiple distinct ways. Hand the same
|
|
1375
|
-
task to two developers who prefer different programming paradigms, say functional vs
|
|
1376
|
-
procedural, and they will end up with two completely different-looking programs even if they
|
|
1377
|
-
write those programs in the same language.</p>
|
|
1378
|
-
<p>Some programming languages are better than others, especially when framed in the
|
|
1379
|
-
context of performing some specific task. Nobody is going to want to write a web application
|
|
1380
|
-
in assembly, but assembly is the perfect language to use when milking every ounce of
|
|
1381
|
-
performance from a machine. JavaScript is a great language for web applications, but many
|
|
1382
|
-
believe that it isn't suited for desktop application development due to its heavy
|
|
1383
|
-
runtime and often poor performance.</p>
|
|
1384
|
-
<p>The other side of the equation is what programming language you know best. C may be
|
|
1385
|
-
the best language for the task, but if you're intimately familiar with Python then it
|
|
1386
|
-
makes sense to do a little more work getting your Python program than learning an entirely
|
|
1387
|
-
new language (especially if that language is infamous for how difficult it is to correctly
|
|
1388
|
-
write).</p>
|
|
1389
|
-
<p>Beginners should learn any general-purpose language that they're excited about
|
|
1390
|
-
unless they have a specific task they want to accomplish. That language might be C++, Java,
|
|
1391
|
-
Go, Python, or JavaScript. There may be better, newer languages out there, but that
|
|
1392
|
-
doesn't really matter. Languages are easy to learn after your first (as long as the
|
|
1393
|
-
paradigms match).</p>
|
|
1394
|
-
<p>Picking a programming language shouldn't be a barrier to those entering the
|
|
1395
|
-
field. We as engineers should put aside our personal preferences and instead let new
|
|
1396
|
-
developers choose whatever language they're excited about.</p>
|
|
1397
|
-
</content:encoded>
|
|
1398
|
-
</item>
|
|
1399
|
-
<item>
|
|
1400
|
-
<title>Rust is Exciting</title>
|
|
1401
|
-
<link>https://sjer.red/blog/2021/rust-is-exciting/</link>
|
|
1402
|
-
<guid isPermaLink="true">https://sjer.red/blog/2021/rust-is-exciting/</guid>
|
|
1403
|
-
<description>Rust has a lot of potential</description>
|
|
1404
|
-
<pubDate>Sat, 19 Jun 2021 00:00:00 GMT</pubDate>
|
|
1405
|
-
<content:encoded><p>Rust is an exciting language. I recently bought The Rust Programming
|
|
1406
|
-
Language Book. It's quite dense with a lot of concepts I haven't thought about
|
|
1407
|
-
since college. Working in high-level programming languages such as Java, Python, and
|
|
1408
|
-
TypeScript have allowed me to mostly forget about the woes of low-level programming. Rust
|
|
1409
|
-
has both re-introduced me to these problems, and then immediately solved them with the
|
|
1410
|
-
advanced static analysis that its compiler provides.</p>
|
|
1411
|
-
<p>I'm still a beginner with Rust. That's the exciting part. I have so many
|
|
1412
|
-
questions; so many things to figure out. It's a challenge. It's a lot to learn. It
|
|
1413
|
-
reminds me of when I first started programming. There was a mountain of work to do, and an
|
|
1414
|
-
endless number of things to figure out. Eventually it gets easier and you become productive.
|
|
1415
|
-
You write small applications just because you can — because you want to prove that you know
|
|
1416
|
-
what you think you know.</p>
|
|
1417
|
-
<p>Rust is full of features. Many of them deal with safety, such as the ownership
|
|
1418
|
-
system and borrow checker. It helps to guarantee memory safety, and coincidentally also
|
|
1419
|
-
helps when writing code that will be executed concurrently. These features are important,
|
|
1420
|
-
but what I'm really excited about are the language features — the features that make a
|
|
1421
|
-
language a joy to work in. Rust has plenty of these. Interoperability with C, pattern
|
|
1422
|
-
matching, the lack of a null type, Cargo, immutability-by-default, functional programming
|
|
1423
|
-
constructs built-in, macros, a compiler with the most helpful error messages I've ever
|
|
1424
|
-
seen, tuples, and pattern-matching. I'm just scratching the service. The type system
|
|
1425
|
-
and syntax isn't quite as good as TypeScript, which I hold as the absolute gold
|
|
1426
|
-
standard (even if it isn't perfect).</p>
|
|
1427
|
-
<p>What's particularly exciting is the applicability of Rust. I tend to use
|
|
1428
|
-
Python or Java for small shell scripts/programs. It works great for me, but it's not
|
|
1429
|
-
especially portable. If I want to share my creations with my team members I have to provide
|
|
1430
|
-
explicit instructions about which runtime versions must be used, and any possible
|
|
1431
|
-
dependencies. Rust is a bit different. Since it produces native binaries I can simply hand
|
|
1432
|
-
them the executable (provided the dependencies are bundled) and let them have at
|
|
1433
|
-
it.</p>
|
|
1434
|
-
<p>I'm excited to be a beginner again. I'm excited to learn about things
|
|
1435
|
-
I'll never use in my day job. I'm excited to get good at writing Rust code.
|
|
1436
|
-
I'm excited for the future of programming languages, which will hopefully follow in
|
|
1437
|
-
Rust's footsteps.</p>
|
|
1438
|
-
</content:encoded>
|
|
1439
|
-
</item>
|
|
1440
|
-
<item>
|
|
1441
|
-
<title>On Perfection</title>
|
|
1442
|
-
<link>https://sjer.red/blog/2021/on-perfection/</link>
|
|
1443
|
-
<guid isPermaLink="true">https://sjer.red/blog/2021/on-perfection/</guid>
|
|
1444
|
-
<description>Being perfect is usually a poor choice</description>
|
|
1445
|
-
<pubDate>Sat, 27 Feb 2021 00:00:00 GMT</pubDate>
|
|
1446
|
-
<content:encoded><p>This is a bit of stream of consciousness with some light editing. I
|
|
1447
|
-
wrote it quickly on my iPad, so it's pretty rough. It's better than nothing
|
|
1448
|
-
though.</p>
|
|
1449
|
-
<p>I've always thought of myself as a perfectionist especially when it comes to
|
|
1450
|
-
the code that I write and the projects that I work on. This tenancy can be useful since it
|
|
1451
|
-
has allowed me to dive deep into many topics, but it also limits my productivity. I've
|
|
1452
|
-
spent so much time learning how to model data and implement data access layer code that I
|
|
1453
|
-
never had the opportunity to build anything of consequence with what I learned. I know quite
|
|
1454
|
-
a lot about Java and the JVM, but it only comes in handy once in a while.</p>
|
|
1455
|
-
<p>Factorio is one of my favorite games. It's a game with a simple premise.
|
|
1456
|
-
You're stuck on an alien planet and need to get off of it by building a factory which
|
|
1457
|
-
can ultimately produce a spaceship.</p>
|
|
1458
|
-
<p>I found that the game perfectly captures many aspects of programming. One thing
|
|
1459
|
-
that it made me realize is the clear trade-off of doing something right versus a
|
|
1460
|
-
quick-and-dirty hack to just get some item in my factory on the production line.</p>
|
|
1461
|
-
<p>I've noticed that I'm much more critical when reading code not written by
|
|
1462
|
-
me. I'll give myself a pass because I had to get this code shipped today, or because I
|
|
1463
|
-
was focused on fixing the real problems. I like writing good code. Not necessarily because
|
|
1464
|
-
it has any concrete, intrinsic value, but because writing good code makes me a programmer. I
|
|
1465
|
-
do think that there are many advantages to my functional, OOP, data-oriented
|
|
1466
|
-
style.</p>
|
|
1467
|
-
<p>It can be especially crippling when I have a deadline and perception that I'm
|
|
1468
|
-
unable to do things right. It's oft said that the best is the enemy of the good, and
|
|
1469
|
-
that's something that I truly struggle with. Why would I do something if I can't
|
|
1470
|
-
do it my way — if I can't do it perfectly? Obviously I'd do it because it needs to
|
|
1471
|
-
be done, but that's not something I get motivation from. That's not why I'm a
|
|
1472
|
-
programmer. I'm a programmer because I enjoy making things that work, with code that I
|
|
1473
|
-
can take pride in.</p>
|
|
1474
|
-
</content:encoded>
|
|
1475
|
-
</item>
|
|
1476
|
-
<item>
|
|
1477
|
-
<title>Introduction</title>
|
|
1478
|
-
<link>https://sjer.red/blog/2021/introduction/</link>
|
|
1479
|
-
<guid isPermaLink="true">https://sjer.red/blog/2021/introduction/</guid>
|
|
1480
|
-
<description>Introducing my blog</description>
|
|
1481
|
-
<pubDate>Tue, 12 Jan 2021 00:00:00 GMT</pubDate>
|
|
1482
|
-
<content:encoded><p>A few months ago I discovered fast.ai's fastbook. It was
|
|
1483
|
-
released in August with an accompanying set of YouTube videos that serves as an introductory
|
|
1484
|
-
course in the topic of deep learning. The content is made very approachable by the authors
|
|
1485
|
-
Jeremy Howard, Rachel Thomas, and Sylvain Gugger.</p>
|
|
1486
|
-
<p>I already had a cursory knowledge of AI having taken a class in college, but I
|
|
1487
|
-
wanted to dive deeper into the inner workings — ideally all the way to the metal. I
|
|
1488
|
-
purchased the book on Amazon in August, but I've been slow to go through it since some
|
|
1489
|
-
of the video lectures are rather long, and I learn far better by reading and doing versus
|
|
1490
|
-
listening.</p>
|
|
1491
|
-
<p>This week I've decided to pick up where I left off. I had been thinking about
|
|
1492
|
-
starting a early today (why not), and one of the sections of the book/lecture specifically
|
|
1493
|
-
calls out the advantages of starting your own blog which is rather strange for a book about
|
|
1494
|
-
deep learning. Anyway, that section of the book pushed me over the edge, so now here we
|
|
1495
|
-
are.</p>
|
|
1496
|
-
<p>To give a quick introduction of myself: My name is Jerred Shepherd. I'm a
|
|
1497
|
-
software engineer working at Amazon Web Services. I work on problems regarding distributed
|
|
1498
|
-
systems and scalability which has been really fun. I love computers and programming, and I
|
|
1499
|
-
often spend my free time working on side projects as a hobby.</p>
|
|
1500
|
-
</content:encoded>
|
|
1501
|
-
</item>
|
|
1502
|
-
<item>
|
|
1503
|
-
<title>Intro to 3D Graphics with OpenGL</title>
|
|
1504
|
-
<link>https://sjer.red/blog/2019/opengl/</link>
|
|
1505
|
-
<guid isPermaLink="true">https://sjer.red/blog/2019/opengl/</guid>
|
|
1506
|
-
<description>An introduction to 3D graphics rendering and OpenGL.</description>
|
|
1507
|
-
<pubDate>Wed, 03 Apr 2019 00:00:00 GMT</pubDate>
|
|
1508
|
-
<content:encoded><p>import cube from
|
|
1509
|
-
"./opengl/img/textured-cube.mp4";</p>
|
|
1510
|
-
<p>This is an adaptation of my senior <a
|
|
1511
|
-
href="https://github.com/shepherdjerred-homework/seminar-paper">seminar
|
|
1512
|
-
paper</a>, originally written in LaTeX. You can view the original <a
|
|
1513
|
-
href="/opengl.pdf">here</a>.</p>
|
|
1514
|
-
<h2>Table of Contents</h2>
|
|
1515
|
-
<h2>Introduction</h2>
|
|
1516
|
-
<p>Computer graphics is an essential component to any consumer facing computer.
|
|
1517
|
-
Efficiently rendering computer graphics requires the use of specialized hardware in the form
|
|
1518
|
-
of graphics processing units. GPUs work differently than the CPUs which programmers are
|
|
1519
|
-
experienced with. This difference is due to the GPUs approach to parallelization. Graphics
|
|
1520
|
-
APIs have been created to help programmers write performant and portable code for GPUs. This
|
|
1521
|
-
post will introduce the core concepts of 3D graphics rendering and OpenGL, a popular and
|
|
1522
|
-
widely-supported graphics API.</p>
|
|
1523
|
-
<p>Computers have become widespread and have touched many aspects of modern-day life.
|
|
1524
|
-
From new forms of entertainment such as video games to artificial intelligence, they have
|
|
1525
|
-
revolutionized the way to work, learn, and play. Computer graphics in particular have been a
|
|
1526
|
-
driving force behind the adoption of computers because they allow users to easily interact
|
|
1527
|
-
with computers. Programmers use computer graphics to display user interfaces, render video
|
|
1528
|
-
games, and even animate entire films. Every day millions of people interact with interfaces
|
|
1529
|
-
on phones, laptops, and other devices that allow them to focus on the work they are doing
|
|
1530
|
-
rather than how they communicate with their device.</p>
|
|
1531
|
-
<p>Plenty of tools to manipulate 2D and 3D graphics already exist, such as: 3D object
|
|
1532
|
-
editors, image processing applications, UI libraries, and game engines. These tools allow
|
|
1533
|
-
programmers to quickly get work done, but they may not know how their work is being
|
|
1534
|
-
accomplished. Without this understanding it may be harder for the programmer to debug or
|
|
1535
|
-
optimize their code. By learning the lower-level concepts of computer graphics, one is able
|
|
1536
|
-
to better use higher-level abstractions and tools.</p>
|
|
1537
|
-
<p>This paper intends to introduce the reader to the broad concepts of graphics
|
|
1538
|
-
processing. This includes the purpose of graphics processing units, why graphics APIs exist,
|
|
1539
|
-
and an introduction to a commonly used cross-platform graphics API --- OpenGL. OpenGL will
|
|
1540
|
-
be discussed so that the reader has an overview of the concepts of OpenGL and can create an
|
|
1541
|
-
OpenGL program. This paper will focus on the use OpenGL version 3.2 which is widely
|
|
1542
|
-
supported and is very similar to the most recent version of OpenGL, which is 4.7.</p>
|
|
1543
|
-
<h2>Background</h2>
|
|
1544
|
-
<p>Computer graphics is predisposed towards parallel processing due to the repetitive
|
|
1545
|
-
and independent nature of the calculations that it requires (p.~4, sellers2016). Graphics
|
|
1546
|
-
processing units (GPUs) were created in order to meet the unique hardware requirements of
|
|
1547
|
-
computer graphics and are fundamentally different from more traditional central processing
|
|
1548
|
-
units (CPUs). While CPUs generally have a few very fast cores, GPUs take another approach by
|
|
1549
|
-
having a large number of slow cores. Figure 1 illustrates this difference in architecture.
|
|
1550
|
-
It shows the massive difference in arithmetic logic units (ALUs) between CPUs and GPUs.
|
|
1551
|
-
These ALUs give GPUs an edge over CPUs when performing mathematical operations and are a
|
|
1552
|
-
core part of the parallel processing capability that GPUs possess.</p>
|
|
1553
|
-
<p>&lt;figure&gt;
|
|
1554
|
-
|
|
1555
|
-
&lt;figcaption&gt;Figure 1, a comparison of typical CPU and GPU architectures
|
|
1556
|
-
(larkin2016).&lt;/figcaption&gt;
|
|
1557
|
-
&lt;/figure&gt;</p>
|
|
1558
|
-
<p>A difference also exists between the two in their approach to threading. An
|
|
1559
|
-
application running on a CPU may generally have only few threads running due to the high
|
|
1560
|
-
cost of creation and context switches. GPUs have lower cost thread creation and can more
|
|
1561
|
-
quickly context switch, and they often require tens of thousands of threads in order to
|
|
1562
|
-
fully reach their processing potential (larkin2016). Despite the slower performance of an
|
|
1563
|
-
individual GPU core, GPUs easily outperform CPUs in tasks related to graphics processing due
|
|
1564
|
-
to their parallel processing capabilities. Figure 2 shows the historical theoretical
|
|
1565
|
-
performance gap between CPUs and GPUs in giga floating-point operations per second
|
|
1566
|
-
(GFLOP/s), which is a common measure of graphical computation speed. The graph shows that
|
|
1567
|
-
GPUs have a clear performance advantage, with the gap becoming exponentially larger as time
|
|
1568
|
-
goes on.</p>
|
|
1569
|
-
<p>&lt;figure&gt;
|
|
1570
|
-
|
|
1571
|
-
&lt;figcaption&gt;
|
|
1572
|
-
Figure 2, a performance comparison of CPUs and GPUs in theoretical peak GLFOP/s over 14
|
|
1573
|
-
years (galloy2013).
|
|
1574
|
-
&lt;/figcaption&gt;
|
|
1575
|
-
&lt;/figure&gt;</p>
|
|
1576
|
-
<p>Using GPUs effectively is especially important when rendering user interfaces,
|
|
1577
|
-
which generally should remain responsive at all times. Because of the architectural
|
|
1578
|
-
differences between GPUs and CPUs, GPUs are programmed differently than CPUs so that their
|
|
1579
|
-
peak performance can be reached. In addition to writing high-level code which is compiled
|
|
1580
|
-
into assembly, GPUs are also programmed by using APIs that are exposed by the manufacturer
|
|
1581
|
-
of the graphics card. GPU manufacturers may support proprietary APIs which is very common on
|
|
1582
|
-
cards that are made to be used in systems such as game consoles and arcade machines, or they
|
|
1583
|
-
may implement standardized APIs such as OpenGL, Metal, Vulkan, or DirectX.</p>
|
|
1584
|
-
<p>While using a proprietary API may allow a programmer to get more performance out of
|
|
1585
|
-
the hardware they are targeting, it will severely limit the application's portability.
|
|
1586
|
-
A program that is written to run on a GPU using OpenGL should be compatible with any other
|
|
1587
|
-
GPU implementing the same version of OpenGL. These APIs help programmers to focus on the
|
|
1588
|
-
code they are writing rather than what hardware the code will be executed on. The APIs are
|
|
1589
|
-
created so that they are at a low enough level that maximum performance can be achieved
|
|
1590
|
-
while also being at a high enough level so that programmers can easily use them (p.~4,
|
|
1591
|
-
sellers2016).</p>
|
|
1592
|
-
<h2>Graphics Rendering</h2>
|
|
1593
|
-
<p>Computer displays, such as monitors, use a 2D matrix of pixels to display images.
|
|
1594
|
-
The dimensions of this matrix are referred to as the display's resolution.
|
|
1595
|
-
Traditionally each pixel of a color display has a red, blue, and green value, which together
|
|
1596
|
-
determine the color of the pixel. Graphics rendering is how the color of these pixels are
|
|
1597
|
-
determined so that objects such as text, images, and user interfaces can be conveyed
|
|
1598
|
-
(mckesson2018). The higher the resolution of a display the more detail that can be shown on
|
|
1599
|
-
elements that are drawn. For reference, displays commonly range from standard HD at
|
|
1600
|
-
1280x720, to very high resolutions such as 3840 x 2160, referred to as 4K. Standard HD is
|
|
1601
|
-
becoming less common while 4K is slowing gaining market share, especially on high-end
|
|
1602
|
-
monitors, TVs, and other consumer electronics.</p>
|
|
1603
|
-
<p>Graphics rendering is done by positioning primitives such as points, lines, and
|
|
1604
|
-
triangles in a 3D space. After these primitives are defined, their locations are
|
|
1605
|
-
transformed, and they are drawn on the screen through a process called rasterization
|
|
1606
|
-
(mckesson2018). Figure 3 shows a simple example of the rasterization process. The figure
|
|
1607
|
-
begins by defining a triangle primitive which is composed of three 3D vectors. The
|
|
1608
|
-
rasterizer then determines which fragments the triangle overlaps through a process called
|
|
1609
|
-
scan conversion. One way to do this is to include pixels if their centers are contained
|
|
1610
|
-
within the element. Rasterization then produces list of fragments. A fragment is an element
|
|
1611
|
-
that contributes to the final color of a pixel (sellers2016). The fragments are assigned a
|
|
1612
|
-
color in later steps of the rendering process, which may be changed even further in the
|
|
1613
|
-
process when effects such as lighting are taken into account.</p>
|
|
1614
|
-
<p>Although these three primitives may seem simple and insignificant, they are
|
|
1615
|
-
actually the backbone of graphics rendering. Complex 3D models such as those shown in video
|
|
1616
|
-
games are really just meshes of many small, connected triangles. Squares and other
|
|
1617
|
-
quadrilaterals can be created by connecting two triangles together at their
|
|
1618
|
-
hypotenuses.</p>
|
|
1619
|
-
<p>&lt;figure&gt;
|
|
1620
|
-
&lt;div class="bg-white"&gt;&lt;/div&gt;
|
|
1621
|
-
&lt;figcaption&gt;Figure 3, a visualization of how a single 2D triangle is
|
|
1622
|
-
rasterized (mckesson2018).&lt;/figcaption&gt;
|
|
1623
|
-
&lt;/figure&gt;</p>
|
|
1624
|
-
<h3>Matrices</h3>
|
|
1625
|
-
<p>A two-dimensional object can be drawn by simply defining its x and y coordinates.
|
|
1626
|
-
Three-dimensional objects will not be drawn correctly because the z coordinate does not
|
|
1627
|
-
affect the size of the object. The z coordinate represents how close a point is to a viewer
|
|
1628
|
-
with larger numbers being closer, therefore as the z coordinate increases the object should
|
|
1629
|
-
increase in size as well. This can be fixed by using a projection matrix and matrix
|
|
1630
|
-
multiplication. A projection matrix effects what is visible on the screen, and how it is
|
|
1631
|
-
projected onto it (sellers2016). Each (x, y, z, w) coordinate will be multiplied by a 4x4
|
|
1632
|
-
matrix, with the result being the location of the coordinate when projected onto the
|
|
1633
|
-
display.</p>
|
|
1634
|
-
<p>Before the matrix can be created, the aspect ratio, field of view (FOV), z-near,
|
|
1635
|
-
and z-far variables must be determined. The aspect ratio is equal to a display's width
|
|
1636
|
-
in pixels divided by its height in pixels. z-near and z-far represent the closest and
|
|
1637
|
-
furthest possible z coordinate values respectively. The FOV is the angle of the scene that
|
|
1638
|
-
will be rendered on the screen. Figure 4 illustrates of these four variables. Figure 5 shows
|
|
1639
|
-
an example of a 4x4 projection matrix used for 3D rendering. Orthogonal projection matrices
|
|
1640
|
-
exist for 2D rendering however they are outside of the scope of this paper.</p>
|
|
1641
|
-
<p>&lt;figure&gt;
|
|
1642
|
-
|
|
1643
|
-
&lt;figcaption&gt;
|
|
1644
|
-
Figure 4, projection matrix concepts (hernandez2019). The camera represents the position of
|
|
1645
|
-
the viewer.
|
|
1646
|
-
&lt;/figcaption&gt;
|
|
1647
|
-
&lt;/figure&gt;</p>
|
|
1648
|
-
<p>&lt;figure&gt;
|
|
1649
|
-
|
|
1650
|
-
&lt;figcaption&gt;
|
|
1651
|
-
Figure 5, a projection matrix where a is the aspect ratio, <code>fov</code> is
|
|
1652
|
-
the field of view, <code>z_f</code> is z-far, and <code>z_n</code>
|
|
1653
|
-
is
|
|
1654
|
-
z-near (hernandez2019).
|
|
1655
|
-
&lt;/figcaption&gt;
|
|
1656
|
-
&lt;/figure&gt;</p>
|
|
1657
|
-
<p>While a projection matrix defines how all rendered geometry appears, a model matrix
|
|
1658
|
-
can be used to transform individual elements. Like a projection matrix, a model matrix is a
|
|
1659
|
-
4v4 matrix. It is used to translate, rotate, and scale individual objects on the screen.
|
|
1660
|
-
Model matrices are very important in 3D rendering, as they allow objects to be positioned
|
|
1661
|
-
precisely in scenes without having to manually calculate vertex locations. Like the
|
|
1662
|
-
projection matrix, the model matrix is applied by multiplying the position of each vertex by
|
|
1663
|
-
the matrix. The result is the new position of the vertex with translation, rotation, and
|
|
1664
|
-
scaling applied.</p>
|
|
1665
|
-
<h2>OpenGL</h2>
|
|
1666
|
-
<p>OpenGL is both a specification and a graphics API which is commonly implemented on
|
|
1667
|
-
modern graphics cards. The specification defines how the use of API should affect the
|
|
1668
|
-
graphics card. Like other graphics APIs, this allows users to use the underlying graphics
|
|
1669
|
-
hardware portably. OpenGL's development began at a computer hardware manufacturer named
|
|
1670
|
-
Silicon Graphics Inc. (SGI). SGI created its own proprietary graphics API named IrisGL which
|
|
1671
|
-
was used on its workstations and graphics hardware. IrisGL was cleaned up and formalized
|
|
1672
|
-
into OpenGL. The first version of OpenGL was released to the public as version 1.0 in 1992
|
|
1673
|
-
(openglwiki2018). This initial version has been revised many times, with the latest version
|
|
1674
|
-
being 4.6, which was released on July 31st, 2017 (openglwiki2018).</p>
|
|
1675
|
-
<p>API bindings exist for C/C++ off of which all other language bindings are based
|
|
1676
|
-
(openglwiki2018). Bindings exist for a wide variety of other languages such as python, C#,
|
|
1677
|
-
and LISP and for every major operating system (openglwiki2018). The Lightweight Java Game
|
|
1678
|
-
Library (lwjgl) is a popular Java library that provides bindings for the OpenGL API and it
|
|
1679
|
-
will be used for example in this paper.</p>
|
|
1680
|
-
<p>Before OpenGL can be used, an OpenGL context (i.e. a desktop window) must be
|
|
1681
|
-
created. Many libraries exist to do this in a cross-platform manner. One popular library
|
|
1682
|
-
that is bundled with lwjgl is the Graphics Library Framework (GLFW), which makes it very
|
|
1683
|
-
easy to create an OpenGL context with lwjgl. Figure 6 shows the code necessary to initialize
|
|
1684
|
-
the GLFW framework, create an operating system window with GLFW, and then finally bind an
|
|
1685
|
-
OpenGL context to the window for drawing. Now that a window and OpenGL context have been
|
|
1686
|
-
created, OpenGL methods can now be called, and the window is ready for drawing.</p>
|
|
1687
|
-
<p>&lt;figure&gt;
|
|
1688
|
-
```java
|
|
1689
|
-
// Initialize GLFW
|
|
1690
|
-
glfwInit();</p>
|
|
1691
|
-
<pre><code>// Create a new window with a given width, height, and title
|
|
1692
|
-
long window = glfwCreateWindow(300, 300, "Hello World!", NULL, NULL);
|
|
1693
|
-
|
|
1694
|
-
// Set the newly created window at the current OpenGL context
|
|
1695
|
-
glfwMakeContextCurrent(window);
|
|
1696
|
-
|
|
1697
|
-
// Show the window
|
|
1698
|
-
glfwShowWindow(window);
|
|
1699
|
-
|
|
1700
|
-
// Creates OpenGL bindings using the current context
|
|
1701
|
-
GL.createCapabilities();
|
|
1702
|
-
```
|
|
1703
|
-
</code></pre>
|
|
1704
|
-
<p>&lt;figcaption&gt;Figure 6&lt;/figcaption&gt;
|
|
1705
|
-
&lt;/figure&gt;</p>
|
|
1706
|
-
<h2>VBOs, VAOs, and Uniforms</h2>
|
|
1707
|
-
<p>OpenGL provides the standard primitives used in graphics rendering --- points,
|
|
1708
|
-
lines, and triangles. All of these primitives are represented as float arrays which can be
|
|
1709
|
-
passed to OpenGL for rendering. These float arrays are stored in vertex buffer objects
|
|
1710
|
-
(VBOs). VBOs are used to allocate memory on graphics hardware and are bound along with other
|
|
1711
|
-
objects to vertex array objects (VAOs). Creating a VBO with OpenGL is a simple task, but it
|
|
1712
|
-
would be beneficial to understand how the OpenGL APIs work before going further into the
|
|
1713
|
-
subject.</p>
|
|
1714
|
-
<p>The OpenGL API has state that is shared globally, which includes what vertex array
|
|
1715
|
-
objects and vertex buffer objects are currently bound. Communication between the CPU and GPU
|
|
1716
|
-
is relatively slow so it should be minimized. Minimizing communication is done by buffering
|
|
1717
|
-
data to be drawn ahead of time and reusing the buffered data whenever possible. Due to the
|
|
1718
|
-
limitations of the Java Virtual Machine (JVM) all memory being shared with native libraries
|
|
1719
|
-
must be allocated off of the JVM heap (lwjglwiki). lwjgl provides utilities to easily
|
|
1720
|
-
allocate this memory through the MemoryStack class. Data created on this stack can be sent
|
|
1721
|
-
to the graphics hardware with lwjgl. Unsigned integers, referred to as names, are used to
|
|
1722
|
-
uniquely identify OpenGL elements such as VBOs and VAOs. Many OpenGL functions take these
|
|
1723
|
-
names as arguments or rely on the currently bound VBO or VAO.</p>
|
|
1724
|
-
<p>Figure 7 shows the code needed to store the vertices of a triangle on a graphics
|
|
1725
|
-
card. Line two asks OpenGL to create a new buffer name and store it. Next that buffer is
|
|
1726
|
-
bound so that all subsequent operations that require an array buffer will use it. An array
|
|
1727
|
-
of floats representing the vertices of a triangle are created, stored in native memory, and
|
|
1728
|
-
the finally transferred to the graphics card on line 22. A VBO is now stored on the graphics
|
|
1729
|
-
card, but a VAO must first be created and bound before the VBO can be used.</p>
|
|
1730
|
-
<p>&lt;figure&gt;
|
|
1731
|
-
```java
|
|
1732
|
-
// Create a VBO and store its name
|
|
1733
|
-
glVboName = glGenBuffers();</p>
|
|
1734
|
-
<pre><code>// Bind the VBO created in the last step
|
|
1735
|
-
glBindBuffer(GL_ARRAY_BUFFER, glVboName);
|
|
1736
|
-
|
|
1737
|
-
float[] vertices = new float[]{
|
|
1738
|
-
0.0f, 0.5f, 0.0f,
|
|
1739
|
-
-0.5f, -0.5f, 0.0f,
|
|
1740
|
-
0.5f, -0.5f, 0.0f
|
|
1741
|
-
};
|
|
1742
|
-
|
|
1743
|
-
try (var stack = MemoryStack.stackPush()) {
|
|
1744
|
-
// Allocate a native buffer to store the vertices
|
|
1745
|
-
var vertexBuffer = stack.mallocFloat(vertices.length);
|
|
1746
|
-
|
|
1747
|
-
// Put the previously declared vertices into the float buffer
|
|
1748
|
-
vertexBuffer.put(vertices);
|
|
1749
|
-
vertexBuffer.flip();
|
|
1750
|
-
|
|
1751
|
-
// Send the vertices to the graphics hardware
|
|
1752
|
-
glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW);
|
|
1753
|
-
}
|
|
1754
|
-
```
|
|
1755
|
-
</code></pre>
|
|
1756
|
-
<p>&lt;figcaption&gt;Figure 7&lt;/figcaption&gt;
|
|
1757
|
-
&lt;/figure&gt;</p>
|
|
1758
|
-
<p>A vertex array object stores state so that drawing can be done quickly and is
|
|
1759
|
-
required to be created before anything can be drawn. A VAO is bound and then set up so that
|
|
1760
|
-
when an object needs to be drawn in the future it only needs to be rebound without any
|
|
1761
|
-
further instruction before drawing. A vertex array simply keeps pointers to buffers which
|
|
1762
|
-
can and should be bound to multiple VAOs to save memory on the graphics hardware. Figure 8
|
|
1763
|
-
shows the relationship between vertex array objects and vertex buffer objects. Notice how
|
|
1764
|
-
the VAO has what is essentially an array of pointers to buffers. OpenGL allows you to set
|
|
1765
|
-
the pointer at each index to buffers that contain information about what is being drawn.
|
|
1766
|
-
Vertex buffer objects often contain vertex position coordinates, but they can contain other
|
|
1767
|
-
data used to render the element such as color and texture data.</p>
|
|
1768
|
-
<p>&lt;figure&gt;
|
|
1769
|
-
|
|
1770
|
-
&lt;figcaption&gt;Figure 8, two VAOs with one VBO each
|
|
1771
|
-
(devries2019).&lt;/figcaption&gt;
|
|
1772
|
-
&lt;/figure&gt;</p>
|
|
1773
|
-
<p>As shown in Figure 9, creating a VAO is similar to creating a VBO. The
|
|
1774
|
-
glGenVertexArrays method allocates space for a VAO on the graphics card and returns its
|
|
1775
|
-
name. The name is then bound with the glBindVertexArray method so that all future methods
|
|
1776
|
-
that require a bound VAO use the newly created VAO. The previously created VBO is rebound on
|
|
1777
|
-
line 8 to ensure that the VAO points to the correct VBO. Line 11 sets the 0th index of the
|
|
1778
|
-
bound VAO to the currently bound VBO object. Its other arguments define the size, type, and
|
|
1779
|
-
layout of the data. The last step is to enable the 0th index on the VAO --- otherwise OpenGL
|
|
1780
|
-
will not pass the buffer when rendering.</p>
|
|
1781
|
-
<p>&lt;figure&gt;
|
|
1782
|
-
```java
|
|
1783
|
-
// Create a VAO and store its name
|
|
1784
|
-
glVaoName = glGenVertexArrays();</p>
|
|
1785
|
-
<pre><code>// Bind the VAO that was just created
|
|
1786
|
-
glBindVertexArray(glVaoName);
|
|
1787
|
-
|
|
1788
|
-
// Bind the previously created VBO
|
|
1789
|
-
glBindBuffer(GL_ARRAY_BUFFER, glVboName);
|
|
1790
|
-
|
|
1791
|
-
// Have the first VAO index point to the bound VBO
|
|
1792
|
-
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
|
|
1793
|
-
|
|
1794
|
-
// Enable the first VAO index
|
|
1795
|
-
glEnableVertexAttribArray(0);
|
|
1796
|
-
```
|
|
1797
|
-
</code></pre>
|
|
1798
|
-
<p>&lt;figcaption&gt;Figure 9&lt;/figcaption&gt;
|
|
1799
|
-
&lt;/figure&gt;</p>
|
|
1800
|
-
<p>VAOs cannot bind more than 4 elements of a VBO array, making it hard to pass large
|
|
1801
|
-
amounts of data to shaders while rendering. Buffers also must be set per VAO which can make
|
|
1802
|
-
code repetitive if some buffer is shared between many VAOs. Uniforms are a type of variable
|
|
1803
|
-
which aim to solve this problem. Uniforms can store not only vectors, but also matrices.
|
|
1804
|
-
They are intended for use where data is common between several elements. Figure 10 shows a
|
|
1805
|
-
uniform being created to store a 4x4 projection matrix.</p>
|
|
1806
|
-
<p>&lt;figure&gt;
|
|
1807
|
-
```java
|
|
1808
|
-
// Create a new perspective matrix
|
|
1809
|
-
var matrix = new Matrix4f().perspective(FIELD_OF_VIEW, aspectRatio, Z_NEAR,
|
|
1810
|
-
Z_FAR);</p>
|
|
1811
|
-
<pre><code>// Get the name of the matrix
|
|
1812
|
-
glUniformName = glGetUniformLocation(glShaderProgramName, "projectionMatrix");
|
|
1813
|
-
|
|
1814
|
-
// Move the matrix to native memory, and then buffer it
|
|
1815
|
-
try (MemoryStack stack = MemoryStack.stackPush()) {
|
|
1816
|
-
FloatBuffer fb = stack.mallocFloat(16);
|
|
1817
|
-
matrix.get(fb);
|
|
1818
|
-
glUniformMatrix4fv(glUniformName, false, fb);
|
|
1819
|
-
}
|
|
1820
|
-
```
|
|
1821
|
-
</code></pre>
|
|
1822
|
-
<p>&lt;figcaption&gt;Figure 10&lt;/figcaption&gt;
|
|
1823
|
-
&lt;/figure&gt;</p>
|
|
1824
|
-
<h3>Indexed Rendering</h3>
|
|
1825
|
-
<p>Consider the case of drawing a square. This would require two right triangles of
|
|
1826
|
-
equal dimensions, each connected at their hypotenuses. Each triangle would only have one
|
|
1827
|
-
unique coordinate, with the other two being shared at the hypotenuses. Right now, this would
|
|
1828
|
-
have to be sent as six coordinates as show in Figure 11, instead of only four. While these
|
|
1829
|
-
two extra coordinates may seem insignificant, this inefficient use of graphics memory may
|
|
1830
|
-
cause problems, especially when rendering complex scenes with hundreds or thousands of
|
|
1831
|
-
triangles. This problem can be solved with indexed rendering.</p>
|
|
1832
|
-
<p>Indexing adds a layer of indirection when rendering. Rather than directly passing
|
|
1833
|
-
the coordinates to draw to OpenGL, a list of indices is passed. OpenGL then uses this list
|
|
1834
|
-
to access the true data in its buffers. The code to create an element buffer is very similar
|
|
1835
|
-
to that of creating VAOs and VBOs, so it will be omitted. Instead, a comparison of the data
|
|
1836
|
-
being passed to OpenGL will be provided.</p>
|
|
1837
|
-
<p>The figure below shows a comparison of the code to draw a square with and without
|
|
1838
|
-
indexing. Notice how on Figure 12 each vertex is defined once, and then the vertices used
|
|
1839
|
-
for each triangle is defined in a separate array. The numbers in the indices array
|
|
1840
|
-
represents which index in the vertices array should be used when drawing. For example, the
|
|
1841
|
-
value 0 in the indices array refers to the 0th entry in the vertices array, which is the
|
|
1842
|
-
top-left vertex.</p>
|
|
1843
|
-
<p>Creating a square without indexing.</p>
|
|
1844
|
-
<p>&lt;figure&gt;
|
|
1845
|
-
<code>java float[] vertices = new float[]{ // Format: x, y, z // Triangle one 0.0f,
|
|
1846
|
-
0.0f, 0.0f, // Top left 0.0f, -1f, 0.0f, // Bottom left 1f, 0.0f, 0.0f, // Top right //
|
|
1847
|
-
Triangle two 0.0f, -1f, 0.0f, // Bottom left -1f, -1f, 0.0f, // Bottom right 0.0f, -1f, 0.0f
|
|
1848
|
-
// Top right }; </code></p>
|
|
1849
|
-
<p>&lt;figcaption&gt;Figure 11, creating a square without
|
|
1850
|
-
indexing.&lt;/figcaption&gt;
|
|
1851
|
-
&lt;/figure&gt;</p>
|
|
1852
|
-
<p>&lt;figure&gt;
|
|
1853
|
-
```java
|
|
1854
|
-
float[] vertices = new float[]{
|
|
1855
|
-
0.0f, 0.0f, 0.0f, // Top left
|
|
1856
|
-
0.0f, -1f, 0.0f, // Bottom left
|
|
1857
|
-
1f, 0.0f, 0.0f, // Top right
|
|
1858
|
-
-1f, -1f, 0.0f, // Bottom right
|
|
1859
|
-
};</p>
|
|
1860
|
-
<pre><code>int[] indices = new int[] {
|
|
1861
|
-
0, 1, 2, // Triangle One
|
|
1862
|
-
0, 2, 3 // Triangle Two
|
|
1863
|
-
};
|
|
1864
|
-
```
|
|
1865
|
-
</code></pre>
|
|
1866
|
-
<p>&lt;figcaption&gt;Figure 12, creating a square with
|
|
1867
|
-
indexing.&lt;/figcaption&gt;
|
|
1868
|
-
&lt;/figure&gt;</p>
|
|
1869
|
-
<h2>The Rendering Pipeline</h2>
|
|
1870
|
-
<p>Now that a VAO and VBO have been created, OpenGL is almost ready to draw. The only
|
|
1871
|
-
thing left to do is to tell OpenGL what to do with the data it receives from VAOs, VBOs, and
|
|
1872
|
-
uniforms. When OpenGL receives a draw command, it runs the VAO through a pipeline in order
|
|
1873
|
-
to determine what to draw and how to draw it. This pipeline is responsible for positioning,
|
|
1874
|
-
rasterizing, and coloring primitives. The pipeline is divided into several stages, many of
|
|
1875
|
-
which are configurable through programs written in the OpenGL shading language (GLSL).
|
|
1876
|
-
Figure 13 shows a simplified view of the OpenGL pipeline and Figure 14 shows how the input
|
|
1877
|
-
is transformed throughout the pipeline. While OpenGL allows several of its stages to be
|
|
1878
|
-
programmed or configured by the user, some of the stages are fixed-function, meaning that
|
|
1879
|
-
OpenGL does not allow any customization to the stage. A couple of notable fixed-function
|
|
1880
|
-
stages include the vertex fetch stage, the primitive assembly stage, and per-sample
|
|
1881
|
-
processing. The vertex fetch stage is the first stage in the pipeline and feeds the pipeline
|
|
1882
|
-
with data from VAOs and VBOs. The primitive assembly stage performs an optimization known as
|
|
1883
|
-
face culling to ensure that only visible triangles are processed. Per-sample processing is
|
|
1884
|
-
the final stage in the OpenGL pipeline and ensures that previously drawn elements are not
|
|
1885
|
-
overwritten unless the new element's depth is higher through a process called depth
|
|
1886
|
-
testing and writes fragments to the framebuffer to be displayed.</p>
|
|
1887
|
-
<p>While there are several configurable stages in the pipeline, only two must be
|
|
1888
|
-
defined for rendering to occur. These two stages are the vertex shader and fragment shader.
|
|
1889
|
-
The vertex shader tells OpenGL where a primitive is located, while the fragment shader
|
|
1890
|
-
contributes to the final color of a primitive during rendering.</p>
|
|
1891
|
-
<p>&lt;figure&gt;
|
|
1892
|
-
|
|
1893
|
-
&lt;figcaption&gt;
|
|
1894
|
-
Figure 13, a simplified OpenGL pipeline (sellers2016). Boxes with square corners are
|
|
1895
|
-
programmable by the user.
|
|
1896
|
-
&lt;/figcaption&gt;
|
|
1897
|
-
&lt;/figure&gt;</p>
|
|
1898
|
-
<p>&lt;figure&gt;
|
|
1899
|
-
&lt;div class="bg-white"&gt;&lt;/div&gt;
|
|
1900
|
-
&lt;figcaption&gt;Figure 14, visualization of data flowing through the OpenGL
|
|
1901
|
-
pipeline (overvoorde2019).&lt;/figcaption&gt;
|
|
1902
|
-
&lt;/figure&gt;</p>
|
|
1903
|
-
<p>In order to customize the OpenGL pipeline a shader program must first be created.
|
|
1904
|
-
Once it is created, shaders can be compiled, attached to the program, and then linked. This
|
|
1905
|
-
final executable is run on the graphics hardware and executed by OpenGL when rendering.
|
|
1906
|
-
Figure 15 shows the code needed to create an OpenGL shader program.</p>
|
|
1907
|
-
<p>&lt;figure&gt;
|
|
1908
|
-
```java
|
|
1909
|
-
// Create IDs for a shader program and vertex shader
|
|
1910
|
-
glShaderProgramName = glCreateProgram();
|
|
1911
|
-
glVertexShaderName = glCreateShader(GL_VERTEX_SHADER);</p>
|
|
1912
|
-
<pre><code>// Associate the shader with a string containing its source code
|
|
1913
|
-
glShaderSource(glVertexShaderName, vertexShaderSource);
|
|
1914
|
-
|
|
1915
|
-
// Compile the stored source for the shader
|
|
1916
|
-
glCompileShader(glVertexShaderName);
|
|
1917
|
-
|
|
1918
|
-
// Associate a shader with a shader program
|
|
1919
|
-
glAttachShader(glShaderProgramName, glVertexShaderName);
|
|
1920
|
-
|
|
1921
|
-
// Create an executable from the attached shaders
|
|
1922
|
-
glLinkProgram(glShaderProgramName);
|
|
1923
|
-
|
|
1924
|
-
// Set OpenGL to use the shader program when rendering
|
|
1925
|
-
glUseProgram(glShaderProgramName);
|
|
1926
|
-
```
|
|
1927
|
-
</code></pre>
|
|
1928
|
-
<p>&lt;figcaption&gt;Figure 15&lt;/figcaption&gt;
|
|
1929
|
-
&lt;/figure&gt;</p>
|
|
1930
|
-
<h3>Writing Shaders</h3>
|
|
1931
|
-
<p>As mentioned before writing shaders is done in the OpenGL shading language which is
|
|
1932
|
-
based off of C. Figure 16 shows a simple vertex shader program. The first line declares the
|
|
1933
|
-
version of OpenGL that the shader is using which is OpenGL 3.3 for this shader. Lines
|
|
1934
|
-
starting with "layout" tell OpenGL what input the shader is expecting. In this
|
|
1935
|
-
case this shader expects two inputs --- vectors of size 3 and size 4. The location indicates
|
|
1936
|
-
which index of the VAO these vectors should be found. The 0th index was set in Figure 16 and
|
|
1937
|
-
represents the position of the element. The creation of the 1st index was omitted for
|
|
1938
|
-
brevity, however as the shader program shows it represents the color of the element. Lines
|
|
1939
|
-
starting with "out" indicate what data this shader passes on to future stages in
|
|
1940
|
-
the pipeline. Uniforms are global variables set. This shader contains only one uniform which
|
|
1941
|
-
is a 4x4 projection matrix.</p>
|
|
1942
|
-
<p>The main function is where the work is being done. gl_Position is a special
|
|
1943
|
-
variable that tells OpenGL the final position of an element. It is assigned to the input
|
|
1944
|
-
position after it is multiplied by the projection matrix. This position is passed on, as
|
|
1945
|
-
well as the color that was passed to the shader.</p>
|
|
1946
|
-
<p>&lt;figure&gt;
|
|
1947
|
-
```glsl
|
|
1948
|
-
#version 330 core</p>
|
|
1949
|
-
<pre><code>// A vector of 3 floats at index 0 of the VAO
|
|
1950
|
-
layout (location = 0) in vec3 position;
|
|
1951
|
-
|
|
1952
|
-
// A vector of 4 floats at index 1 of the VAO
|
|
1953
|
-
layout (location = 1) in vec4 inColor;
|
|
1954
|
-
|
|
1955
|
-
// Output a vector of 4 floats
|
|
1956
|
-
out vec4 color;
|
|
1957
|
-
|
|
1958
|
-
// A global 4x4 matrix
|
|
1959
|
-
uniform mat4 projectionMatrix;
|
|
1960
|
-
|
|
1961
|
-
void main() {
|
|
1962
|
-
// Transform the position of the vertex by the projection matrix
|
|
1963
|
-
gl_Position = projectionMatrix \* vec4(position, 1.0);
|
|
1964
|
-
|
|
1965
|
-
// Pass the color to the fragment shader
|
|
1966
|
-
color = inColor;
|
|
1967
|
-
}
|
|
1968
|
-
```
|
|
1969
|
-
</code></pre>
|
|
1970
|
-
<p>&lt;figcaption&gt;Figure 16&lt;/figcaption&gt;
|
|
1971
|
-
&lt;/figure&gt;</p>
|
|
1972
|
-
<p>The fragment shader partially determines the color of pixels. Figure 17 shows a
|
|
1973
|
-
basic fragment shader which receives a color from the vertex shader in Figure 16 and outputs
|
|
1974
|
-
the color as-is.</p>
|
|
1975
|
-
<p>&lt;figure&gt;
|
|
1976
|
-
```glsl
|
|
1977
|
-
#version 330 core</p>
|
|
1978
|
-
<pre><code>// A vector of 4 floats in
|
|
1979
|
-
in vec4 color;
|
|
1980
|
-
|
|
1981
|
-
// A vector of 4 floats out
|
|
1982
|
-
out vec4 outColor;
|
|
1983
|
-
|
|
1984
|
-
void main() {
|
|
1985
|
-
// Set the outgoing color to the incoming color
|
|
1986
|
-
outColor = color;
|
|
1987
|
-
}
|
|
1988
|
-
```
|
|
1989
|
-
</code></pre>
|
|
1990
|
-
<p>&lt;figcaption&gt;Figure 17&lt;/figcaption&gt;
|
|
1991
|
-
&lt;/figure&gt;</p>
|
|
1992
|
-
<h3>Textures</h3>
|
|
1993
|
-
<p>Textures allow images to be stored and used while rendering. While OpenGL supports
|
|
1994
|
-
many kinds of textures, only standard 2D textures will be covered here. The process for
|
|
1995
|
-
creating a texture follows the same pattern as VBOs. A texture name must first be created
|
|
1996
|
-
and bound, and then data can be loaded into the texture. This code is show in Figure 18.
|
|
1997
|
-
Once the texture is initialized, it can be used in a fragment shader.</p>
|
|
1998
|
-
<p>The fragment shader uses a special kind of uniform type called a sampler. Samplers
|
|
1999
|
-
represent textures in shaders (sellers2016). The fragment shader uses a special function
|
|
2000
|
-
called <code>texture</code>, which takes a sampler and a coordinate. The
|
|
2001
|
-
coordinate represents where in the texture image the fragment should get its data from. In
|
|
2002
|
-
example 19 the shader receives the coordinate from the vertex shader.</p>
|
|
2003
|
-
<p>&lt;figure&gt;
|
|
2004
|
-
```java
|
|
2005
|
-
// Create and bind a texture
|
|
2006
|
-
glTextureName = glGenTextures();
|
|
2007
|
-
glBindTexture(GL_TEXTURE_2D, glTextureName);</p>
|
|
2008
|
-
<pre><code>// Load a texture into the bound texture buffer
|
|
2009
|
-
glTexImage2D(GL_TEXTURE_2D,
|
|
2010
|
-
0,
|
|
2011
|
-
GL_RGBA,
|
|
2012
|
-
width,
|
|
2013
|
-
height,
|
|
2014
|
-
0,
|
|
2015
|
-
GL_RGBA,
|
|
2016
|
-
GL_UNSIGNED_BYTE,
|
|
2017
|
-
imageData);
|
|
2018
|
-
```
|
|
2019
|
-
</code></pre>
|
|
2020
|
-
<p>&lt;figcaption&gt;Figure 18, creating a texture buffer and loading image
|
|
2021
|
-
data into it.&lt;/figcaption&gt;
|
|
2022
|
-
&lt;/figure&gt;</p>
|
|
2023
|
-
<p>&lt;figure&gt;
|
|
2024
|
-
```glsl
|
|
2025
|
-
#version 330 core</p>
|
|
2026
|
-
<pre><code>vec2 textureCoord;
|
|
2027
|
-
|
|
2028
|
-
out vec4 outColor;
|
|
2029
|
-
|
|
2030
|
-
// Uses the bound texture
|
|
2031
|
-
uniform sampler2D textureSampler;
|
|
2032
|
-
|
|
2033
|
-
void main() {
|
|
2034
|
-
outColor = texture(textureSampler, textureCoord);
|
|
2035
|
-
}
|
|
2036
|
-
```
|
|
2037
|
-
</code></pre>
|
|
2038
|
-
<p>&lt;figcaption&gt;Figure 19, loading texture data in the fragment
|
|
2039
|
-
shader.&lt;/figcaption&gt;
|
|
2040
|
-
&lt;/figure&gt;</p>
|
|
2041
|
-
<h2>Conclusion</h2>
|
|
2042
|
-
<p>With all of these concepts a basic OpenGL program can be created. Figure 20 shows a
|
|
2043
|
-
program (Full program source available <a
|
|
2044
|
-
href="https://github.com/ShepherdJerred-homework/seminar-application">here</a>)
|
|
2045
|
-
that renders a simple textured cube that rotates. It uses all of the previously covered
|
|
2046
|
-
material, and most of the code samples used above. While this program is simple, it composes
|
|
2047
|
-
many concepts and lays the foundation for more complex applications. Games can be created
|
|
2048
|
-
with only these concepts. The only further work with graphics would be to render more
|
|
2049
|
-
elements and make the objects more complex.</p>
|
|
2050
|
-
<p>&lt;figure&gt;
|
|
2051
|
-
&lt;video controls&gt;
|
|
2052
|
-
&lt;source src={cube} type="video/mp4" /&gt;
|
|
2053
|
-
&lt;/video&gt;
|
|
2054
|
-
&lt;figcaption&gt;Figure 20, a spinning textured cube.&lt;/figcaption&gt;
|
|
2055
|
-
&lt;/figure&gt;</p>
|
|
2056
|
-
<p>Computer graphics are crucial to providing easy-to-use experiences for users, and
|
|
2057
|
-
in creating modern day entertainment and other visual media. OpenGL allows you to render
|
|
2058
|
-
graphics by writing simple shader programs and feeding the OpenGL pipeline with input data.
|
|
2059
|
-
This data can then efficiently be rendered and displayed on-screen to users. The pipeline
|
|
2060
|
-
can transform input and allows programmers to modify how their input is rendered. While
|
|
2061
|
-
there is a lot of overhead in graphics rendering, it allows the user to get the most out of
|
|
2062
|
-
their graphics hardware.</p>
|
|
2063
|
-
<h2>References</h2>
|
|
2064
|
-
<pre><code>@misc{hernandez2019,
|
|
2065
|
-
author = "Antonio Hernández Bejarano",
|
|
2066
|
-
title = "Lwjglgamedev",
|
|
2067
|
-
url = "ahbejarano.gitbook.io/lwjglgamedev/",
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
@misc{devries2019,
|
|
2071
|
-
author = "Joey de Vries",
|
|
2072
|
-
title = "Learn OpenGL",
|
|
2073
|
-
url = "learnopengl.com/"
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
@misc{galloy2013,
|
|
2077
|
-
author = "Michael Galloy",
|
|
2078
|
-
title = "CPU vs GPU Performance.",
|
|
2079
|
-
url = "michaelgalloy.com/2013/06/11/cpu-vs-gpu-performance.html"
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
@misc{larkin2016,
|
|
2083
|
-
author = "Jeff Larkin",
|
|
2084
|
-
title = "GPU Fundamentals.",
|
|
2085
|
-
url =
|
|
2086
|
-
"www.icl.utk.edu/~luszczek/teaching/courses/fall2016/cosc462/pdf/GPU\_Fundamentals.pdf"
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
@misc{lwjgl,
|
|
2090
|
-
author = "lwjgl",
|
|
2091
|
-
title = "Lightweight Java Game Library",
|
|
2092
|
-
url = "www.lwjgl.org"
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
@misc{lwjglwiki,
|
|
2096
|
-
author = "lwjgl wiki",
|
|
2097
|
-
title = "Lwjgl Wiki",
|
|
2098
|
-
url = "github.com/LWJGL/lwjgl3-wiki/"
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
@misc{masserann2018,
|
|
2102
|
-
author = "Arnaud Masserann",
|
|
2103
|
-
title = "OpenGL Tutorial",
|
|
2104
|
-
url = "www.opengl-tutorial.org/"
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
@misc{mckesson2018,
|
|
2108
|
-
author = "Jason L. McKesson",
|
|
2109
|
-
title = "Learning Modern 3D Graphics Programming.",
|
|
2110
|
-
url = "paroj.github.io/gltut/"
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
@misc{openglwiki2018,
|
|
2114
|
-
author = "Khronos",
|
|
2115
|
-
title = "OpenGL Wiki",
|
|
2116
|
-
url = "www.khronos.org/opengl/wiki/Main\_Page"
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
@misc{overvoorde2019,
|
|
2120
|
-
author = "Alexander Overvoorde",
|
|
2121
|
-
title = "OpenGL Tutorial",
|
|
2122
|
-
url = "open.gl/"
|
|
2123
|
-
}
|
|
2124
|
-
|
|
2125
|
-
@book{sellers2016,
|
|
2126
|
-
author = "Graham Sellers, et al.",
|
|
2127
|
-
title = "OpenGL Superbible: Comprehensive Tutorial and Reference",
|
|
2128
|
-
year = "2016",
|
|
2129
|
-
publisher = "Addison-Wesley"
|
|
2130
|
-
}
|
|
2131
|
-
</code></pre>
|
|
2132
|
-
</content:encoded>
|
|
2133
|
-
</item>
|
|
2134
|
-
</channel>
|
|
2135
|
-
</rss>
|