Instruction file imported from yoyonel-lab/suckless-odin (
.github/instructions/odin-coding-style.instructions.md). Copyright stays with the author.
Odin Coding Style
Vet & Strict Style Compliance
All code must pass: odin check src/ -vet -strict-style -warnings-as-errors
This enforces:
- No unused variables or imports
- No variable shadowing
- 1TBS brace style (opening brace on same line)
- Trailing commas in multi-line struct literals and proc args
- No deprecated syntax
Type Conventions
- Use
f32for all floating-point values (matches GPU/GLSL) - Use
i32for dimensions, counts, indices - Use
u32for OpenGL handles (textures, programs, buffers, VAOs) - Use
boolfor boolean values (never integers) - Use
stringfor text (notcstringunless interfacing with C/OpenGL)
Naming Conventions
snake_casefor procedures, variables, fieldsPascal_Casefor types (structs, enums, unions)UPPER_CASEfor constants- Package-qualified access:
gl.GenTextures,glfw.CreateWindow
Odin Idioms to Prefer
deferfor cleanup (GL resources, allocations)or_returnfor error propagation- Multiple return values instead of out-parameters
#soafor data-oriented arrays (sphere instances)- Tagged unions for variant types
contextsystem for allocators and logging- Named return values when they clarify intent
OpenGL Interop Patterns
// GL handle types — always u32
program: u32
texture: u32
vao, vbo: u32
// String conversion for GL calls
gl.ShaderSource(shader, 1, &raw_data(src), nil)
// Cleanup with defer
gl.GenTextures(1, &texture)
defer if texture != 0 { gl.DeleteTextures(1, &texture) }
Struct Initialization
// Prefer named fields
camera := Camera{
position = Vec3{0, 0, 25},
yaw = -90.0,
pitch = 0.0,
fov = 45.0,
}
Error Handling
- Return
boolfor operations that can fail - Log errors at the point of detection
- Use
or_returnfor propagation chains - Never panic in library code — only in main/tests
Import Organization
- Core library (
core:fmt,core:math,core:os, etc.) - Vendor packages (
vendor:OpenGL,vendor:glfw,vendor:stb) - Project packages (relative imports)
Memory Ownership & defer Discipline
Every allocation MUST have a corresponding deallocation. Before writing or reviewing any code, actively verify the following checklist:
Mandatory defer delete patterns
| Allocation source | Required cleanup |
|---|---|
os.read_entire_file / os.read_entire_file_from_path |
defer delete(data) immediately after error check |
strings.concatenate |
defer delete(result) if temporary |
strings.clone / strings.clone_to_cstring |
defer delete(...) if temporary, or freed in the owning struct's destroy |
strings.builder_make |
Either defer strings.builder_destroy(&b), or ownership transferred via return |
make([]T, ...) / make([dynamic]T) |
defer delete(slice) or freed in destroy proc |
new(T) |
defer free(ptr) or freed in destroy proc |
Rules
- Immediate
defer: If the allocation is used only within the current scope, placedefer delete(...)on the very next line after the error check. - Ownership transfer: If the allocation is returned to the caller, the caller is responsible. Document this in a comment if non-obvious.
- Struct-owned allocations: Any allocation stored in a struct field MUST be freed in that struct's
destroy/cleanup proc. Thedestroyproc MUST iterate dynamic arrays and free each element's owned allocations before deleting the array itself. - Review trigger: When modifying or creating ANY proc that calls an allocating function, STOP and verify: "Where is this freed?" If the answer is not immediately obvious, add the
deferor fix thedestroy. - No silent leaks: LeakSanitizer (ASAN) is the final arbiter. Run
task build-sanitize+ clean shutdown to validate 0 leaks after any memory-related change.
GUI Search Synchronization
Every interactive GUI element MUST be discoverable via the parameter search system.
When adding or modifying any ImGui control in src/gui/gui.odin:
- Add a matching entry in
draw_filtered_view— include afuzzy_match(filter, "Label", "keywords...")block for each new control. - Update the relevant
*_KEYWORDSconstant — add keywords that a user might type to discover the new control. - If the control lives in a sub-window/tab — add a
Go Tobutton (viaibl_goto_buttonpattern or equivalent) that clears the search buffer, activates the target view, and scrolls to the relevant section. - Keyword coverage — keywords must include: the label text, the subsystem name, synonyms, and related technical terms (e.g., "roughness" for a mip level slider).
This rule ensures the search bar acts as a universal command palette — no GUI element should be "invisible" to the search system.