Skip to content

Commit 245370e

Browse files
committed
change(/wiki/bloxels): a lil bit of progress on this monstrosity
1 parent 136182f commit 245370e

File tree

1 file changed

+173
-30
lines changed

1 file changed

+173
-30
lines changed

content/wiki/bloxels/index.md

Lines changed: 173 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ This is a advanced guide that'll walk you through the **theory** and **implement
1717
We assume you have read the [Introduction to Voxels](/wiki/introduction) beforehand.
1818
{% end %}
1919

20+
{% warn_notice() %}
21+
This article was written with an unfortunate amount of anger and sarcasm. We do not apologize.
22+
{% end %}
23+
2024
## Introduction
2125

2226
> 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
3943

4044
---
4145

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
4347

4448
### How NOT to (store) Bloxel
4549

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-
4751

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*\*
4955
50-
-which is an *absolutely terrible* idea.
56+
-which is an <span style="color:red;font-weight:bold">absolutely terrible</span> idea.
5157

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...
5359

5460
{% info_notice(float=true) %}
5561
This is *one* of *the* major reasons why Minecraft's performance has been getting worse over the years.
5662
{% end %}
5763

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+
<small style=";font-size:0.6em">And that's *without* taking Garbage Collection into account.</small>
5966

6067
Noticing that this is happening is pretty hard, until it's too late,
6168
so better not do it in the first place; your CPU and RAM will thank you!
6269

70+
### How to properly store Bloxels
71+
6372
<!--
64-
---
73+
**Compression** is the **bread and butter of voxels**, and thus bloxels.
6574
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>
6776
68-
You need to heed just one word: **Compression**
77+
So how, and how much, can we compress our bloxels?
6978
-->
7079

71-
### How to actually store Bloxels
80+
Let us first establish some general assumptions:
7281

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.
7484

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.
7687

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>.
7890

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.
8093

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.
8596

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+
<small style=";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>
88100

89-
#### Bloxels Are Mostly Unchanging
101+
That said, let's begin with...
90102

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
94104

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.
98114
115+
class AirBloxel extends BloxelType
116+
class ThingBloxel extends BloxelType
117+
class StuffBloxel extends BloxelType
118+
// etc.
99119
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 <q class=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+
---
100242

101-
{% todo_notice() %} Flyweight Pattern {% end %}
102-
{% todo_notice() %} The Global Palette {% end %}
103-
{% todo_notice() %} Chunking {% end %}
104243
{% todo_notice() %} Chunk Management (TLAS) {% end %}
105244
{% todo_notice() %} Chunk Palettes {% end %}
106245

@@ -120,4 +259,8 @@ but rather a very important optimization strategy: **doing nothing is highly per
120259

121260
---
122261

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+
123264
[^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

Comments
 (0)