Skip to content

Commit 5a38c23

Browse files
author
Stephen Gutekanst
committed
article: "Packed structs in Zig make bit/flag sets trivial"
Signed-off-by: Stephen Gutekanst <[email protected]>
1 parent 74fb066 commit 5a38c23

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
author: "Stephen Gutekanst"
3+
title: "Packed structs in Zig make bit/flag sets trivial"
4+
date: "2022-08-29"
5+
draft: false
6+
categories:
7+
- zig
8+
- zigtips
9+
description: "As we've been building Mach engine, we've been using a neat little pattern in Zig that enables writing flag sets more nicely in Zig than in other languages. Here's a brief explainer."
10+
images: ["https://user-images.githubusercontent.com/3173176/184735519-cc78d19d-73e8-4914-8f3d-fc3a15d00bb7.png"]
11+
---
12+
13+
As we've been building [Mach engine](https://machengine.org/), we've been using a neat little pattern in Zig that enables writing flag sets more nicely in Zig than in other languages.
14+
15+
## What is a flag set?
16+
17+
We've been rewriting `mach/gpu` (WebGPU bindings for Zig) from scratch recently, so let's take a flag set from the WebGPU C API:
18+
19+
```c
20+
typedef uint32_t WGPUFlags;
21+
typedef WGPUFlags WGPUColorWriteMaskFlags;
22+
```
23+
24+
Effectively, `WGPUColorWriteMaskFlags` here is a 32-bit unsigned integer where you can set specific bits in it to represent whether or not to write certain colors:
25+
26+
```c
27+
typedef enum WGPUColorWriteMask {
28+
WGPUColorWriteMask_None = 0x00000000,
29+
WGPUColorWriteMask_Red = 0x00000001,
30+
WGPUColorWriteMask_Green = 0x00000002,
31+
WGPUColorWriteMask_Blue = 0x00000004,
32+
WGPUColorWriteMask_Alpha = 0x00000008,
33+
WGPUColorWriteMask_All = 0x0000000F,
34+
WGPUColorWriteMask_Force32 = 0x7FFFFFFF
35+
} WGPUColorWriteMask;
36+
```
37+
38+
Then to use it you'd use the various bit operations with those masks, e.g.:
39+
40+
```c
41+
WGPUColorWriteMaskFlags mask = WGPUColorWriteMask_Red | WGPUColorWriteMask_Green;
42+
mask |= WGPUColorWriteMask_Blue; // set blue bit
43+
```
44+
45+
This all works, people have been doing it for years in C, C++, Java, Rust, and more. In Zig, we can do better.
46+
47+
## Zig packed structs
48+
49+
<img class="color-auto" style="max-height: 300px;" src="https://user-images.githubusercontent.com/3173176/184735519-cc78d19d-73e8-4914-8f3d-fc3a15d00bb7.png" />
50+
51+
Zig has `packed struct`s: these let us pack memory tightly, where a `bool` is actually a single bit (in most other languages, this is not true.) Zig also has arbitrary bit-width integers, like `u28`, `u1` and so on.
52+
53+
We can write `WGPUColorWriteMaskFlags` from earlier in Zig using:
54+
55+
```zig
56+
pub const ColorWriteMaskFlags = packed struct {
57+
red: bool = false,
58+
green: bool = false,
59+
blue: bool = false,
60+
alpha: bool = false,
61+
62+
_padding: u28 = 0,
63+
};
64+
```
65+
66+
This is still just 32 bits of memory, and so can be passed to the same C APIs that expect a `WGPUColorWriteMaskFlags` - but interacting with it is much nicer:
67+
68+
```zig
69+
var mask = ColorWriteMaskFlags{.red = true, .green = true};
70+
mask.blue = true; // set blue bit
71+
```
72+
73+
In C you would need to write code like this:
74+
75+
```c
76+
if (mask & WGPUColorWriteMask_Alpha) {
77+
// alpha is set..
78+
}
79+
if (mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) {
80+
// alpha and blue are set..
81+
}
82+
if ((mask & WGPUColorWriteMask_Green) == 0) {
83+
// green not set
84+
}
85+
```
86+
87+
In Zig it's just:
88+
89+
```zig
90+
if (mask.alpha) {
91+
// alpha is set..
92+
}
93+
if (mask.alpha and mask.blue) {
94+
// alpha is set..
95+
}
96+
if (!mask.green) {
97+
// green not set
98+
}
99+
```
100+
101+
## Comptime validation
102+
103+
Making sure that our `ColorWriteMaskFlags` ends up being the same size could be a bit tricky: what if we count the number of `bool` wrong? Or what if we accidently get the padding size wrong? Then it might not be the same size as a `uint32` anymore.
104+
105+
Luckily, we can verify our expectations at comptime:
106+
107+
```zig
108+
pub const ColorWriteMaskFlags = packed struct {
109+
red: bool = false,
110+
green: bool = false,
111+
blue: bool = false,
112+
alpha: bool = false,
113+
114+
_padding: u28 = 0,
115+
116+
comptime {
117+
std.debug.assert(@sizeOf(@This()) == @sizeOf(u32));
118+
std.debug.assert(@bitSizeOf(@This()) == @bitSizeOf(u32));
119+
}
120+
}
121+
```
122+
123+
The Zig compiler will take care of running the `comptime` code block here for us when building, and it will verify that the byte size of `@This()` (the type we're inside of, the `ColorWriteMaskFlags` struct in this case) matches the `@sizeOf(u32)`.
124+
125+
Similarly we could check the `@bitSizeOf` both types if we like.
126+
127+
Note that [`@sizeOf`](https://ziglang.org/documentation/master/#sizeOf) may include the size of padding for more complex types, while [`@bitSizeOf`](https://ziglang.org/documentation/master/#bitSizeOf) returns the number of bits it takes to store `T` in memory _if the type were a field in a packed struct/union_. For flag sets like this, it doesn't matter and either will do. For more complex types, be sure to recall this.
128+
129+
130+
## Thanks for reading
131+
132+
<img align="left" style="max-height: 150px;" src="https://user-images.githubusercontent.com/3173176/187348488-0b52e87d-3a48-421c-9402-be78e32b5a20.png"></img>
133+
Be sure to join the new [Mach engine Discord server](https://discord.gg/XNG3NZgCqp) where we're building the future of Zig game development.
134+
<br><br>
135+
You can also [sponsor my work](https://github.com/sponsors/slimsag) if you like what I'm doing! :)

content/2022/zig-hashmaps-explained.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ date: "2022-01-29"
55
draft: false
66
categories:
77
- zig
8-
- hashmaps
8+
- zigtips
99
description: "If you just got started with Zig, you might quickly want to use a hashmap. Zig provides good defaults, with a lot of customization options."
1010
images: ["/img/hexops-opengraph.png"]
1111
---

0 commit comments

Comments
 (0)