Pointers are changing when being passed through functions

Setup

  1. Raylib Version: 5.5
  2. Zig Version: 0.15.2

Overview:

I am new to game development overall so, excuse my ignorance, but I am trying to make a game using Raylib Library using ZIG lang, but when I was trying to pass a loaded texture in an array item through a function using the pointer parameter to the Entity object, the texture is not loaded correctly, I was able to make this work only to pass the loaded texture to the Entity instead of pass just a pointer.

NOTE: I want to use the array to store textures, then use them everywhere to save memory.

Code

This is the whole Code which not working properly.

const std = @import("std");
const rl = @cImport({
    @cInclude("raylib.h");
});

const Entity = struct {
    position: rl.Vector2,
    texture: ?*rl.Texture2D,
    fn Init(pos: rl.Vector2, texture: ?*rl.Texture2D) Entity {
        return .{
            .position = pos,
            .texture = texture,
        };
    }

    fn Draw(self: *Entity) void {
        if (self.texture) |texture| {
            rl.DrawTexture(
                texture.*,
                @intFromFloat(self.position.x),
                @intFromFloat(self.position.y),
                rl.WHITE,
            );
        } else {
            rl.DrawRectangle(
                @intFromFloat(self.position.x),
                @intFromFloat(self.position.y),
                100,
                100,
                rl.ORANGE,
            );
        }
    }
};

const Map = struct {
    tileset: [3]rl.Texture2D,
    entities: [5]?Entity,

    fn Init() Map {
        var map: Map = undefined;

        map.tileset[0] = rl.LoadTexture("./res/imgs/red.png");
        map.tileset[1] = rl.LoadTexture("./res/imgs/ground.png");
        map.tileset[2] = rl.LoadTexture("./res/imgs/blue.png");

        const layer: [5]u8 = .{ 1, 2, 0, 3, 1 };
        for (layer, 0..) |item, index| {
            if (item != 0) {
                map.entities[index] = Entity.Init(
                    .{ .x = 30 + 100 * @as(f32, @floatFromInt(index)), .y = 3 },
                    &map.tileset[item - 1],
                );
            } else {
                map.entities[index] = null;
            }
        }

        return map;
    }

    fn DeInit(self: *Map) void {
        rl.UnloadTexture(self.tileset[0]);
        rl.UnloadTexture(self.tileset[1]);
        rl.UnloadTexture(self.tileset[2]);
    }

    fn Draw(self: *Map) void {
        for (&self.entities) |*entity_nl| {
            if (entity_nl.*) |*entity| {
                entity.Draw();
            }
        }
    }
};

pub fn main() !void {
    rl.InitWindow(1920, 1080, "Texture Pointer");
    defer rl.CloseWindow();
    rl.SetTargetFPS(60);

    var map = Map.Init();
    defer map.DeInit();

    while (!rl.WindowShouldClose()) {
        rl.BeginDrawing();
        defer rl.EndDrawing();

        rl.ClearBackground(rl.BEIGE);
        map.Draw();
    }
}

Result:

Working Code:

See the Change comments:

const std = @import("std");
const rl = @cImport({
    @cInclude("raylib.h");
});

// Entity Object
const Entity = struct {
    position: rl.Vector2,
// Change #1: Here I am Removing The Pointer
    texture: ?rl.Texture2D,
// Change #2: Here I am Removing The Pointer also from texture
    fn Init(pos: rl.Vector2, texture: ?rl.Texture2D) Entity {
        return .{
            .position = pos,
            .texture = texture,
        };
    }

    fn Draw(self: *Entity) void {
        if (self.texture) |texture| {
// Change #3: Here I am Removing The dereferencing from texture
            rl.DrawTexture(
                texture,
                @intFromFloat(self.position.x),
                @intFromFloat(self.position.y),
                rl.WHITE,
            );
        } else {
            rl.DrawRectangle(
                @intFromFloat(self.position.x),
                @intFromFloat(self.position.y),
                100,
                100,
                rl.ORANGE,
            );
        }
    }
};

const Map = struct {
    tileset: [3]rl.Texture2D,
    entities: [5]?Entity,

    fn Init() Map {
        var map: Map = undefined;

        map.tileset[0] = rl.LoadTexture("./res/imgs/red.png");
        map.tileset[1] = rl.LoadTexture("./res/imgs/ground.png");
        map.tileset[2] = rl.LoadTexture("./res/imgs/blue.png");

        const layer: [5]u8 = .{ 1, 2, 0, 3, 1 };
        for (layer, 0..) |item, index| {
            if (item != 0) {
                map.entities[index] = Entity.Init(
                    .{ .x = 30 + 100 * @as(f32, @floatFromInt(index)), .y = 3 },
// Change #4: Here I am removing the &
                    map.tileset[item - 1],
                );
            } else {
                map.entities[index] = null;
            }
        }

        return map;
    }

    fn DeInit(self: *Map) void {
        rl.UnloadTexture(self.tileset[0]);
        rl.UnloadTexture(self.tileset[1]);
        rl.UnloadTexture(self.tileset[2]);
    }

    fn Draw(self: *Map) void {
        for (&self.entities) |*entity_nl| {
            if (entity_nl.*) |*entity| {
                entity.Draw();
            }
        }
    }
};
pub fn main() !void {
    rl.InitWindow(1920, 1080, "Texture Pointer");
    defer rl.CloseWindow();
    rl.SetTargetFPS(60);

    var map = Map.Init();
    defer map.DeInit();

    while (!rl.WindowShouldClose()) {
        rl.BeginDrawing();
        defer rl.EndDrawing();

        rl.ClearBackground(rl.BEIGE);
        map.Draw();
    }
}

I hope my explanation is to the point.

It would be nice to point to the difference between the two code blocks instead of asking us to do an MK1-Eyeball search :wink:

2 Likes

Ah the good old “address to item on the stack” problem :wink:

…here, map lives on the stack:

fn Init() Map {
        var map: Map = undefined;

…next you’re storing an address of a nested item in map:

map.entities[index] = Entity.Init(
                    /// ....
                    &map.tileset[item - 1],

…and finally on function exit, the map on the stack is destroyed (what’s returned is a by-value-copy which is usually a good idea, just don’t take an address from such transient objects):

        return map;
    }

…that means that the address you stored in the entities is now pointing to some random data because the original map object you took the address from is no longer alive after Map.Init() exits.

PS: lol, Claude is soooo close to getting it :wink:

1 Like

You could just store the texture index and pass a pointer to the map to the draw call so that it can access the texture. Using an index in the Entity avoids that the Map becomes a structure that is self-referential via pointers and thus can’t be moved.

Another possibility (if you want to keep the pointer to the texture) would be to allocate Map on the stack on the caller side and pass a *Map to Map.Init (to make sure map has a stable location from the beginning):

    fn Init(map: *Map) void {
        map.tileset[0] = rl.LoadTexture("./res/imgs/red.png");
        map.tileset[1] = rl.LoadTexture("./res/imgs/ground.png");
        map.tileset[2] = rl.LoadTexture("./res/imgs/blue.png");

        const layer: [5]u8 = .{ 1, 2, 0, 3, 1 };
        for (layer, 0..) |item, index| {
            if (item != 0) {
                map.entities[index] = Entity.Init(
                    .{ .x = 30 + 100 * @as(f32, @floatFromInt(index)), .y = 3 },
                    &map.tileset[item - 1],
                );
            } else {
                map.entities[index] = null;
            }
        }
    }

    ...
    var map: Map = undefined;
    Map.Init(&map);
    defer map.DeInit();
1 Like

Sorry for that, I will edit right away

Nice, Now I understand why the problem is happening.

Thanks

Thanks, this is a good idea. I didn’t think about it