You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -17,6 +17,10 @@ This is a advanced guide that'll walk you through the **theory** and **implement
17
17
We assume you have read the [Introduction to Voxels](/wiki/introduction) beforehand.
18
18
{% end %}
19
19
20
+
{% warn_notice() %}
21
+
This article was written with an unfortunate amount of anger and sarcasm. We do not apologize.
22
+
{% end %}
23
+
20
24
## Introduction
21
25
22
26
> Pssst, hey, wanna clone Minecraft? Well, here you go!
@@ -39,68 +43,203 @@ we won't do that here as it has been done a thousand times over (that horse has
39
43
40
44
---
41
45
42
-
Instead, let's talk about the most common mistake people make! ;D
46
+
Instead, let's talk about *the* first and single most common mistake people make! ;D
43
47
44
48
### How NOT to (store) Bloxel
45
49
46
-
Due to the endemic of people learning [Object-Oriented Programming](https://en.wikipedia.org/wiki/Object-oriented_programming), without ever learning about [Data-Driven Programming](https://en.wikipedia.org/wiki/Data-driven_programming), a lot of people go-
50
+
Due to the endemic of people learning {{ref(id="wikipedia-oop")}}, without ever learning about {{ref(id="wikipedia-data-driven-programming")}}, the complex limits of computation and caching, or even how computers *actually work* on the lowest levels (<small>ever heard of an ALU[^ALU]?</small>), a lot of people go-
47
51
48
-
> I know! Let's make `Bloxel` a class and inherit from that!
52
+
> I know! Let's make `Bloxel` a <small>(boxed)</small> class and inherit from that!
53
+
>
54
+
> \**proceeds to create a bajillion `Bloxel` instances*\*
49
55
50
-
-which is an *absolutely terrible* idea.
56
+
-which is an <spanstyle="color:red;font-weight:bold">absolutely terrible</span> idea.
51
57
52
-
Creating large arrays of heap-based objects doesn't seem like a problem at first, computers are *really fast* after all...
58
+
Creating large arrays of individually heap-allocated objects doesn't seem like a problem at first, computers are *really fast* after all...
53
59
54
60
{% info_notice(float=true) %}
55
61
This is *one* of *the* major reasons why Minecraft's performance has been getting worse over the years.
56
62
{% end %}
57
63
58
-
But as the voxel volume grows and more bloxels are added, we eventually begin pulling *too many things*, over *too big a range* of memory into our [CPU cache](https://en.wikipedia.org/wiki/CPU_cache), invalidating it more and more, meaning we have to pull *even more* things from memory... and this costs [a lot of time](https://gist.github.com/jboner/2841832).
64
+
But as the voxel volume grows and more bloxels are added, we eventually begin pulling *too many things*, over *too big a range* of memory into our {{ref(id="wikipedia-cpu-cache")}}, invalidating it more and more, meaning we have to pull *even more* things from memory... which costs {{ref(id="latency",body="a lot of time")}}.
65
+
<smallstyle=";font-size:0.6em">And that's *without* taking Garbage Collection into account.</small>
59
66
60
67
Noticing that this is happening is pretty hard, until it's too late,
61
68
so better not do it in the first place; your CPU and RAM will thank you!
62
69
70
+
### How to properly store Bloxels
71
+
63
72
<!--
64
-
---
73
+
**Compression** is the **bread and butter of voxels**, and thus bloxels.
65
74
66
-
So, how does one avoid these pitfalls and make things work?
75
+
Without it our worlds would be smol, our memory full, our CPU cache trashing, frame-time climbing, fans screaming... <small>you get the idea.</small>
67
76
68
-
You need to heed just one word: **Compression**
77
+
So how, and how much, can we compress our bloxels?
69
78
-->
70
79
71
-
### How to actually store Bloxels
80
+
Let us first establish some general assumptions:
72
81
73
-
**Compression** is the **bread and butter of voxels**, and thus bloxels.
82
+
-**Bloxels are Voxels:**
83
+
This means they very much *should* be stored in [chunks](/wiki/chunking), and *not* have an explicit position as part of their data.
74
84
75
-
Without it our worlds would be smol, our memory full, our CPU cache trashing, frame-time climbing, fans screaming... you get the idea.
85
+
-**Bloxels are mostly static:**
86
+
Unless one is building something, blowing things up, actively flooding a valley, or messing with some insane Redstone contraption, bloxels just don't *do* anything on their own.
76
87
77
-
So how, and how much, can we compress our bloxels?
88
+
-**Bloxels are mostly the same:**
89
+
Even if a scene looks very complex, chances are that if one picks *any* visible bloxel, there will many duplicates of that bloxel around, <small>ignoring variations due to rendering and lighting</small>.
78
90
79
-
Let's establish some rules-of-thumb:
91
+
-**Bloxels are mostly hidden:**
92
+
Given an average scene of earthly terrain, the vast *vast* majority[^mc-block-count] of bloxels will be hidden below the surface and behind other bloxels.
80
93
81
-
- Bloxels are mostly static.
82
-
- Bloxels are mostly the same.
83
-
- Bloxels are mostly hidden.
84
-
- Bloxels tend to be 'piled up'.
94
+
-**Bloxels tend to be 'piled up':**
95
+
Aside from some truly silly scenarios, bloxels are clumped/clustered together as terrain, flora, structures, etc.
85
96
86
-
<!--
87
-
So, let's start with the first thing: doing absolutely nothing!
97
+
Keeping ^these^ in mind is *very* important, else you'll spend a lot of time
98
+
thinking/planning for situations that *just don't happen*.
99
+
<smallstyle=";font-size:0.6em">Disregarding players creating [tiny floating islands](https://minecraft.wiki/w/Tutorial:Skyblock) and [endless grids](https://modrinth.com/plugin/skygridx).</small>
88
100
89
-
#### Bloxels Are Mostly Unchanging
101
+
That said, let's begin with...
90
102
91
-
If you look at the average Minecraft gameplay video, you may have noticed that,
92
-
outside of (in)direct player interaction, all the visible bloxels are just *there*,
93
-
not doing *anything*, millions upon millions[^mc-block-count] of them!
103
+
#### The Global Palette
94
104
95
-
This is not due to the developers being lazy or uninspired with making their bloxels *do* stuff,
96
-
but rather a very important optimization strategy: **doing nothing is highly performant**.
97
-
-->
105
+
Whatever type of bloxel game you are building, you will almost certainly have a **Global Palette**: A collection of bloxel definitions/types that is used and referenced[^flyweight-pattern]*everywhere*.
106
+
107
+
```pseudocode
108
+
// Using classes and inheritance
109
+
// is perfectly fine here!
110
+
abstract class BloxelType:
111
+
id: integer
112
+
name: string
113
+
// etc.
98
114
115
+
class AirBloxel extends BloxelType
116
+
class ThingBloxel extends BloxelType
117
+
class StuffBloxel extends BloxelType
118
+
// etc.
99
119
120
+
static GLOBAL_PALETTE: List<BloxelType> = [ ... ]
121
+
```
122
+
123
+
{% info_notice() %}
124
+
If you want to make the global palette data/config-driven, use a single `class BloxelType`, with fields and callbacks for *all* the attributes and behaviours your blocks can have, then create and define instances of `BloxelType` by reading files from a directory/zip on start-up.
125
+
{% end %}
126
+
127
+
With our global palette at hand, we can now proceed to creating...
128
+
129
+
#### Chunks of Bloxels
130
+
131
+
A *chunk of bloxels* is nothing more than a fixed-size container,
132
+
for a small cubic volume (8³ to 64³) of bloxels.
133
+
134
+
```pseudocode
135
+
// A small fixed-size cubic volume,
136
+
// representing a section of the world.
137
+
class BloxelChunk:
138
+
const SIZE = 8 // any power-of-two
139
+
const SLICE = SIZE * SIZE
140
+
const VOLUME = SIZE * SIZE * SIZE
141
+
142
+
// SIZE-scale position of chunk in the world.
143
+
position: (int,int,int)
144
+
145
+
// The backing storage of bloxels for this chunk.
146
+
storage: BloxelStorage<VOLUME>
147
+
148
+
// Assert the coordinate-component is in bounds.
149
+
limit(v:int): int
150
+
=> match v:
151
+
case v >= SIZE: throw OutOfBoundsError
152
+
case v < 0: throw OutOfBoundsError
153
+
case v: v
154
+
155
+
// Return index of bloxel in chunk by location.
156
+
index(x:int, y:int, z:int): int
157
+
=> limit(x)
158
+
+ (limit(y) * SIZE)
159
+
+ (limit(z) * SLICE)
160
+
161
+
// Return location of bloxel in chunk by index.
162
+
locate(index:int): (x:int, y:int, z:int) {
163
+
if index < 0: throw OutOfBoundsError
164
+
if index >= VOLUME: throw OutOfBoundsError
165
+
166
+
// (this can also be done via bit-twiddling!)
167
+
let z = index % SIZE; index / SIZE;
168
+
let y = index % SIZE; index / SIZE;
169
+
let x = index % SIZE;
170
+
(x, y, z)
171
+
}
172
+
173
+
get(x:int, y:int, z:int): BloxelState
174
+
=> storage[index(x,y,z)]
175
+
176
+
set(x:int, y:int, z:int, state:BloxelState)
177
+
=> storage[index(x,y,z)] = state
178
+
```
179
+
180
+
The actual storage of the bloxel volume is separate from the chunk itself,
181
+
because converting coordinates to indices is *only* necessary at the chunk level,
182
+
which keeps our [concerns separate](https://en.wikipedia.org/wiki/Separation_of_concerns),
183
+
but *also* simplifies things for later and allows us to replace our storage implementation,
184
+
if necessary.
185
+
186
+
For now, to keep things reasonably simple and short,
187
+
we will just directly store `BloxelState`'s in a plain ol' array,
188
+
to be replaced with a [palette compressed](/wiki/palette-compression) storage later...
189
+
190
+
```pseudocode
191
+
interface BlockStorage<const CAPACITY>:
192
+
get(index: int): BlockState
193
+
set(index: int, state: BlockState)
194
+
195
+
class ArrayBlockStorage implements BlockStorage:
196
+
instances: [BlockState; CAPACITY]
197
+
198
+
get(index: int)
199
+
case index < 0 throw OutOfBoundsError
200
+
case index >= CAPACITY throw OutOfBoundsError
201
+
=> instances[index]
202
+
203
+
set(index: int, state: BlockState)
204
+
case index < 0 throw OutOfBoundsError
205
+
case index >= CAPACITY throw OutOfBoundsError
206
+
=> instances[index] = state
207
+
```
208
+
209
+
If you're wondering <qclass=fancy>what the hecc is a `BloxelState`?!</q>, well...
210
+
211
+
## Bloxel States
212
+
213
+
Time for a quick thought experiment:
214
+
Let's define the *properties* of our terrains *surface*!
215
+
...or rather, all the grass covering it, to keep things trimmed.
216
+
217
+
So, what kind of properties could a patch of grass have?
218
+
219
+
- Moisture level?
220
+
- Temperature?
221
+
- Muddiness?
222
+
- Coverage?
223
+
224
+
Seems simple enough; let's struct-ify that!
225
+
226
+
```pseudocode
227
+
@Derive(Equality)
228
+
struct GrassState:
229
+
temperature: AVERAGE | FROZEN
230
+
moisture: AVERAGE | DRY | WET
231
+
coverage: AVERAGE | BARE | DENSE
232
+
mud: NONE | LOW | HIGH
233
+
```
234
+
235
+
Now we have a state definition! But... how do we use this?
236
+
237
+
238
+
239
+
240
+
241
+
---
100
242
101
-
{% todo_notice() %} Flyweight Pattern {% end %}
102
-
{% todo_notice() %} The Global Palette {% end %}
103
-
{% todo_notice() %} Chunking {% end %}
104
243
{% todo_notice() %} Chunk Management (TLAS) {% end %}
105
244
{% todo_notice() %} Chunk Palettes {% end %}
106
245
@@ -120,4 +259,8 @@ but rather a very important optimization strategy: **doing nothing is highly per
120
259
121
260
---
122
261
262
+
[^ALU]: [*Arithmetic Logic Unit*](https://en.wikipedia.org/wiki/Arithmetic_logic_unit): The fundamental building block (<small>no pun intended</small>) of any integrated circuit capable of computation.
263
+
123
264
[^mc-block-count]: With a default view-distance of 12 chunks, there are approximately **+20 million _solid_ blocks** surrounding the player in Minecraft.
265
+
266
+
[^flyweight-pattern]: {{ref(id="wikipedia-flyweight-pattern")}}: An enormously large collection of *simple things* is described by a *much* smaller set of *complex things*, reducing memory usage at the (utterly negligible) cost of a single indirection.
0 commit comments