*ฅ^•ﻌ•^ฅ* ✨✨  HWisnu's blog  ✨✨ о ฅ^•ﻌ•^ฅ

Bunnymarks in Zig + Raylib

Introduction

You can find the whole introduction to Raylib on the first Bunnymarks post here, I'm too lazy to rewrite them in this article.

Section 1: Importing Libraries and Defining Constants

This section imports the standard library (std) and the Raylib library (rl). It also defines several constants for the program, including the window dimensions, caption, number of bunnies, and bunny properties.

const std = @import("std");
const rl = @import("raylib");

const print = std.debug.print;
const allocator = std.heap.page_allocator;

const WIDTH: i32 = 800;
const HEIGHT: i32 = 600;
const CAPTION: *const [9:0]u8 = "Bunnymark";

const BUNNIES: i32 = 100000;
const BUNNY_MAX_SPEED: i32 = 10;
const BUNNY_WIDTH: i32 = 10;
const BUNNY_HEIGHT: i32 = 10;

Section 2: Defining the Bunny Structure and Functions

Defines a randInt function to generate random integers within a specified range. It also defines a Bunny structure to represent individual bunnies, with properties for position, speed, and color. The Bunny structure includes methods to initialize, update, and paint bunnies.

Note: I'm using methods/functions on struct

fn randInt(low: i32, up: i32) i32 {
    return rng.random().intRangeAtMost(i32, low, up);
}

const Bunny = struct {
    x: i32,
    y: i32,
    speedx: i32,
    speedy: i32,
    color: rl.Color,
    
    fn init(x: i32, y: i32, sx: i32, sy: i32, c: rl.Color) Bunny {
        return Bunny {
            .x = x,
            .y = y,
            .speedx = sx,
            .speedy = sy,
            .color = c,
        };
    }
    
    fn update(self: *Bunny) void {
        self.x += self.speedx;
        self.y += self.speedy;
        
        if (self.x < 0 or self.x > WIDTH) {
            self.speedx *= -1;
        }
        if (self.y < 0 or self.y > HEIGHT) {
            self.speedy *= -1;
        }
    }
    
    fn paint(self: Bunny) void {
        rl.drawRectangle(
            self.x, 
            self.y, 
            BUNNY_WIDTH, 
            BUNNY_HEIGHT, 
            self.color);
    }
};

Section 3: Defining the update and paint Functions

Defines two functions to update and paint the bunnies. The update function updates the position of each bunny, while the paint function clears the background, paints each bunny, and displays the FPS and number of bunnies on the screen.

fn update() void {
    var i: usize = 0;
    while (i < bunnies.len) : (i += 1) {
        bunnies[i].update();
    }
}

fn paint() !void {
    rl.clearBackground(rl.Color.ray_white);

    var i: usize = 0;
    while (i < bunnies.len) : (i += 1) {
        bunnies[i].paint();
    }

    const printFPS = try std.fmt.bufPrint(&printFPS_buffer, "FPS: {}", .{rl.getFPS()});
    rl.drawText(@ptrCast(printFPS.ptr), 5, 5, 30, rl.Color.black);

    const printBunnies = try std.fmt.bufPrint(&printBunnies_buffer, "BUNNIES: {}", .{BUNNIES});
    rl.drawText(@ptrCast(printBunnies.ptr), 5, 45, 30, rl.Color.black);
}

Section 4: Main Function

The main function of the program. It initializes the window, initializes each bunny with random properties, and enters a loop where it updates and paints the bunnies until the window is closed.

pub fn main() !void {

    rl.initWindow(WIDTH, HEIGHT, CAPTION);
    defer rl.closeWindow();

    for (&bunnies) |*bunny| {
        bunny.* = Bunny.init(
            randInt(0, WIDTH), 
            randInt(0, HEIGHT), 
            randInt(1, BUNNY_MAX_SPEED), 
            randInt(1, BUNNY_MAX_SPEED), 
            rl.Color {
                .r = @as(u8, @intCast(randInt(0, 255))),
                .g = @as(u8, @intCast(randInt(0, 255))),
                .b = @as(u8, @intCast(randInt(0, 255))),
                .a = 255,
        });
    }

    while (!rl.windowShouldClose()) {
        update();
        rl.beginDrawing();
        try paint();
        rl.endDrawing();
    }
}

BONUS section: commented out code

Below is a snippet of code that works fine, but is around 10% slower compared to the ForLoop implementation (above) which uses pointer and dereference. The WhileLoop implementation utilizes array indexing, which is actually safer due to the inherent bounds checking.

    // Using the array indexing in a While Loop works but slower.
    var i: usize = 0;
    while (i < bunnies.len) : (i += 1) {
        bunnies[i].x = randInt(0, WIDTH);
        bunnies[i].y = randInt(0, HEIGHT);
        bunnies[i].speedx = randInt(1, BUNNY_MAX_SPEED);
        bunnies[i].speedy = randInt(1, BUNNY_MAX_SPEED);

        bunnies[i].color.r = @as(u8, @intCast(randInt(0, 255)));
        bunnies[i].color.g = @as(u8, @intCast(randInt(0, 255)));
        bunnies[i].color.b = @as(u8, @intCast(randInt(0, 255)));
        bunnies[i].color.a = 255;
    }

Benchmark

The result for Zig + Raylib benchmark is super interesting, as it scores 403 FPS in my system. I've checked several times and I think the code is correct.

Comparatively Bunnymarks ran at 83 FPS for the C + Raylib version, up to now I've yet to understand what's causing this huge difference in performance.

#Bunnymarks #benchmark #game development #low level #raylib #zig