Skip to content

Commit

Permalink
Merge pull request #114 from karl-zylinski/code-generation
Browse files Browse the repository at this point in the history
Code generation
  • Loading branch information
karl-zylinski authored Mar 3, 2025
2 parents da650a1 + 7b632bc commit 77dc087
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
odin check console/raw_console -target:windows_amd64 $FLAGS
odin check console/read_console_input $FLAGS
odin check code_generation $FLAGS
odin check code_generation/generate_image_info $FLAGS
odin check arena_allocator $FLAGS
odin check directx/d3d12_triangle_sdl2 -target:windows_amd64 $FLAGS
Expand Down
29 changes: 29 additions & 0 deletions code_generation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
This shows some basic code generation / meta programming.

The code generation is done by running a separate program that generates some Odin code.

Generate `images.odin` by running:

```
odin run generate_image_info
```

The following will build the program that actually uses `images.odin`:

```
odin run .
```

It will print:

```
Long_Cat is 9 x 46 pixels and 183 bytes large
Round_Cat is 20 x 24 pixels and 317 bytes large
Round_Cat has width > 15, so we loaded it!
The loaded PNG image is indeed 20 pixels wide!
Tuna is 24 x 20 pixels and 318 bytes large
Tuna has width > 15, so we loaded it!
The loaded PNG image is indeed 24 pixels wide!
```
83 changes: 83 additions & 0 deletions code_generation/generate_image_info/generate_image_info.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
This program generates `images.odin` by going through the `images` folder and
opening each file. From each PNG file in there it will:
- Generate a pretty enum name for it
- Make a list of images where it maps each pretty enum name to an Image struct
- The Image struct contains the width and the height. This is determined by
opening the PNG files.
- The Image struct also contains a `data = #load(THE_FILENAME)` field. That will
make the compiler that later tries to compile `images.odin` load the file
data at compile-time.
*/
package generate_image_info

import "core:os"
import "core:strings"
import "core:fmt"
import "core:path/slashpath"
import "core:image/png"
import "core:image"

// Avoids 'unused import' error: "core:image/png" needs to be imported in order
// to make `img.load_from_bytes` understand PNG format.
_ :: png

INPUT_DIR :: "images"
OUTPUT_FILE :: "images.odin"

main :: proc() {
d, d_err := os.open(INPUT_DIR, os.O_RDONLY)
assert(d_err == nil, "Failed opening '" + INPUT_DIR + "' folder")
defer os.close(d)

input_files, _ := os.read_dir(d, -1)

f, _ := os.open(OUTPUT_FILE, os.O_WRONLY | os.O_CREATE | os.O_TRUNC)
defer os.close(f)

images: [dynamic]os.File_Info

for i in input_files {
if !strings.has_suffix(i.name, ".png") {
continue
}

append(&images, i)
}

fmt.fprintln(f,
`// This file is generated. Re-generate it by running:
// odin run generate_image_info
package image_viewer
Image :: struct {
width: int,
height: int,
data: []u8,
}
Image_Name :: enum {`,
)

for i in images {
fmt.fprintfln(f, " %v,", strings.to_ada_case(slashpath.name(i.name)))
}

fmt.fprintln(f,
`}
images := [Image_Name]Image {`,
)

for i in images {
img, img_err := image.load_from_file(i.fullpath)

if img_err == nil {
enum_name := strings.to_ada_case(slashpath.name(i.name))
fmt.fprintfln(f, " .%v = {{ data = #load(\"images/%v\"), width = %v, height = %v }},", enum_name, i.name, img.width, img.height)
}
}

fmt.fprintln(f, "}")
}
21 changes: 21 additions & 0 deletions code_generation/images.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// This file is generated. Re-generate it by running:
// odin run generate_image_info
package image_viewer

Image :: struct {
width: int,
height: int,
data: []u8,
}

Image_Name :: enum {
Long_Cat,
Round_Cat,
Tuna,
}

images := [Image_Name]Image {
.Long_Cat = { data = #load("images/long_cat.png"), width = 9, height = 46 },
.Round_Cat = { data = #load("images/round_cat.png"), width = 20, height = 24 },
.Tuna = { data = #load("images/tuna.png"), width = 24, height = 20 },
}
Binary file added code_generation/images/long_cat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added code_generation/images/round_cat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added code_generation/images/tuna.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions code_generation/main.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package image_viewer

import "core:image"
import "core:image/png"

// Avoids 'unused import' error: "core:image/png" needs to be imported in order
// to make `img.load_from_bytes` understand PNG format.
_ :: png

import "core:fmt"

/*
This program prints:
Long_Cat is 9 x 46 pixels and 183 bytes large
Round_Cat is 20 x 24 pixels and 317 bytes large
Round_Cat has width > 15 we loaded it!
It is indeed 20 pixels wide!
Tuna is 24 x 20 pixels and 318 bytes large
Tuna has width > 15 we loaded it!
It is indeed 24 pixels wide!
Note how it knows width and height before it loads the file. That was written
into `images.odin` by the code generation program in the `generate_image_info`
folder. It also knows the file size: `img.data` contains the data (i.e. the file
contents) for that image. That data is put into the executable at compile time
using the built-in `#load` procedure. The path of the image send into `#load`
is written by the code generation program.
*/
main :: proc() {
for &img, name in images {
fmt.printfln("%v is %v x %v pixels and %v bytes large", name, img.width, img.height, len(img.data))

// Make decisions based pre-computed data before actually loading the image
if img.width > 15 {
loaded_img, loaded_img_err := image.load_from_bytes(img.data)

if loaded_img_err == nil {
fmt.printfln("%v has width > 15, so we loaded it!", name)
fmt.printfln("The loaded PNG image is indeed %v pixels wide!", loaded_img.width)
}
}

fmt.println()
}
}

0 comments on commit 77dc087

Please sign in to comment.