#Create array of fct ptr with comptime parameter

1 messages · Page 1 of 1 (latest)

celest vale
#

Hi,

In my program, the size of the board can change at runtime, but I know the bounds are between 5 and 20 (inclusive). I have the following function:

pub fn findBestMove(size: comptime_int) Threat

I want to create an array of "function calls" that allows me to invoke the appropriate function based on the size determined at runtime. The compiler should generate the function 15 times, each time with a different size.

Here’s my attempt to achieve that, but it doesn’t work:

const AIMapping = *const fn (comptime_int) ai.Threat;

fn generateFunction(comptime N: usize) AIMapping {
    return fn () ai.Threat {
        ai.findBestMove(N)
    };
}

fn generateAIMap() [15]AIMapping {
    return comptime {
        var arr: [15]AIMapping = undefined;
        for (0..15) |i| {
            // Generate and assign the specialized function for index i
            arr[i] = generateFunction(i);
        }
        arr;
    };
}

const AIMap = generateAIMap();

pub fn AIPlay() [2]u16 {
    const empty_cell = AIMap[board.game_board.width];
    board.game_board.setCellByCoordinates(empty_cell.col, empty_cell.row, board.Cell.own);
    return .{empty_cell.col, empty_cell.row};
}```

How can I achieve what I want ?
long prawn
#

can you generate bare functions and return them as values? that would be news to me, but i know you can make a struct method and return a member:```c++
const std = @import("std");

const Returned = *const fn () comptime_int;

fn getFn(comptime n: comptime_int) Returned {
return struct {pub fn inner() comptime_int {
return n;
}}.inner;
}

pub fn main() void {
std.debug.print("test {}\n", .{ getFn(50)() });
} // output: test 50```

boreal cobalt
#

In zig you can just do away with the table entirely, this is how you "convert" a runtime value to a compile time one:

const empty_cell = switch(board.game_board.width) {
  inline 5..20 => |N| ai.findBestMove(N),
  else => unreachable,
};
#

also I'm not sure how an ai.Threat is convertible to a cell but I'm sure you can see what I'm doing in the code

celest vale
#

thanks both of you 👍

boreal cobalt
boreal cobalt
#

yeah it's probably fine up to a couple hundred or so

#

I mean depends on how fast you want your code to run/how it's being hit

#

if you're hitting it with the same size every time it's gonna start predicting the branches and keep things in cache

#

no matter the table size

long prawn
boreal cobalt
#

you don't want it to un-inline calls to functions with unique compile time params

#

if a given function instantiation is only called once, you always want it inlined

long prawn
#

oh right, that would mean it is not really the same function being called

boreal cobalt
#

yep

#

no matter how you solve the call/lookup thing here you're gonna have many functions generated here

celest vale
#

is there a way to know what is comptime or not ? may I need to look for something in the assembly ?

autumn charm
#

Once you have assembly, you're already past comptime.

static glen
#

is there a way to know what is comptime or not ? may I need to look for something in the assembly ?
I think this may be too general a question to answer concisely.
if you have a specific case and you're trying to figure out from assembly if it got converted to comptime or not, that is probably a bit easier to answer.

#

I guess to try to answer generically: If zig doesn't have a chance of calling something at runtime, and only calls it at comptime, that code won't end up in your assembly at all. the results of running that code might end up in your assembly (e.g. return values).

boreal cobalt
static glen
#

that works for a field. and tbh doesn't seem that cursed to me, either. it's verbose, but quite typical for reflections-style code.
but I'm guessing it doesn't work in a general sort of sense?

boreal cobalt
#

for a field?

#

it works for any value value

static glen
#

yeah, but not everything is a field I mean

boreal cobalt
#

?

static glen
#

I am sorta guessing OP wants to apply it to
const AIMap = generateAIMap();

#

or const empty_cell, per your suggestion

boreal cobalt
#

I'll wait for french boi to reply instead

static glen
#

heh

boreal cobalt
#

why speculate when he can just tell us

#

I'm also not sure what you even mean by it not working

static glen
#

const empty_cell isn't a field, is it?
I'm not sure what it is. I don't know all the names of bits of the grammar yet (like I don't know what a "decl" is, for example)

median pulsar
#

@static glen when you initialize a tuple/anon struct field with a comptime-known value, it generates a type where that value is encoded into the type as a comptime field - this means if the value passed in as the value is comptime-known, the is_comptime property of the field it was used to initialize would be true

#

so it doesn't matter if you're talking about a field, a variable, a decl, or whatever else, it's generally applicable

static glen
#

ah okay I get it. doing the anonymous struct thing so you can get a field for value to reflect off of does bring it slightly closer to cursed territory. but it's still not that bad imo 😄
I was thinking this was going to require some variant of @TypeOf(@This()) to get to the meat of it, but that anonymous tuple struct thingy solves it.

#

Sorry to spam your topic, but just to put a bow on it:

const std = @import("std");

fn getFn(comptime n: comptime_int) fn () void {
    return struct {
        pub fn func() void {
            for (0..n) |i| {
                std.log.info("GOT HERE: {}", .{i});
            }
        }
    }.func;
}

const fns = brk: {
    var result: [5]*const fn () void = undefined;
    for (0..5) |i| {
        result[i] = getFn(i);
    }
    break :brk result;
};

pub fn main() void {
    for (fns, 0..) |func, i| {
        std.log.info("RUNNING FUNC: {}", .{i});
        func();
    }

    std.log.info("{}", .{@typeInfo(@TypeOf(.{fns})).@"struct".fields[0].is_comptime});
}
celest vale
#

My problem is that instead of getting the width from the struct board, I pass it using a comptime parameter size but it made my code slower ???

#

why

static glen
#

Are you calling something via function pointer now that you were calling just normally before?

celest vale
#

here is the function :

// Minimax algorithm with alpha-beta pruning
pub fn minimax(current_board: *board.Board, depth: u8, comptime isMaximizing: bool, alpha_in: i32, beta_in: i32, comptime size: u32) i32 {
    // Base case: evaluate position when depth is reached
    if (depth == 0) {
        return evaluatePosition(current_board);
    }

    var threats: [size * size]Threat = undefined;

    const player = comptime if (isMaximizing) board.Cell.own else board.Cell.opponent;

    const nb_threats = findThreats(current_board, &threats, player);

    if (isMaximizing) {
        // Maximizing player's turn
        var maxScore: i32 = std.math.minInt(i32);
        var alpha = alpha_in;
        var i: u16 = 0;
        while (i < nb_threats): (i += 1) {
            const index = threats[i].row * size + threats[i].col;
            current_board.map[index] = board.Cell.own;
            const score = minimax(current_board, depth - 1, false, alpha, beta_in, comptime size);
            current_board.map[index] = board.Cell.empty;
            maxScore = @max(maxScore, score);
            alpha = @max(alpha, score);
            if (beta_in <= alpha) {
                break; // Beta cutoff
            }
        }
        return maxScore;
    } else {
        // Minimizing player's turn
        var minScore: i32 = std.math.maxInt(i32);
        var beta = beta_in;
        var i: u16 = 0;
        while (i < nb_threats): (i += 1) {
            const index = threats[i].row * size + threats[i].col;
            current_board.map[index] = board.Cell.opponent;
            const score = minimax(current_board, depth - 1, true, alpha_in, beta, comptime size);
            current_board.map[index] = board.Cell.empty;
            minScore = @min(minScore, score);
            beta = @min(beta, score);
            if (beta <= alpha_in) {
                break; // Alpha cutoff
            }
        }
        return minScore;
    }
}```
#

it's weird because when remove the comptime from the size parameter it makes the code faster

median pulsar
#

might be able to better deduplicate it in such a way that is friendlier to cache, hard to say

#

probably depends on how it's being called

celest vale
#

it's recursive

celest vale
# median pulsar probably depends on how it's being called

this function make the initial calls:

// Finds the best move for the AI using minimax algorithm
pub fn findBestMove(comptime size: comptime_int) Threat {
    var current_board = &board.game_board;
    var bestScore: i32 = std.math.minInt(i32);
    var bestMove: Threat = Threat{ .row = 0, .col = 0, .score = 0 };
    var threats: [size * size]Threat = undefined;

    const nb_threats = findThreats(current_board, &threats, board.Cell.own);

    // Check for immediate winning moves
    var i: u16 = 0;
    while (i < nb_threats): (i += 1) {
        if (threats[i].score >= 100000) {
            return threats[i];
        }
    }

    // Use minimax to evaluate moves
    var alpha: i32 = std.math.minInt(i32);
    const beta: i32 = std.math.maxInt(i32);

    i = 0;
    while (i < nb_threats): (i += 1) {
        current_board.setCellByCoordinates(threats[i].col, threats[i].row, board.Cell.own);
        const score = minimax(current_board, 4 - 1, false, alpha, beta, comptime size);
        current_board.setCellByCoordinates(threats[i].col, threats[i].row, board.Cell.empty);

        if (score > bestScore) {
            bestScore = score;
            bestMove = threats[i];
        }
        alpha = @max(alpha, score);
    }
    return bestMove;
}