~mil/mepo

8580f31045cba701e93b99bef378a612ea1ad4ab — Miles Alan a month ago f6fcdbf + d1c4588
Merge branch 'prefs-rework'
M src/Mepo.zig => src/Mepo.zig +24 -31
@@ 7,6 7,7 @@ const TileCache = @import("./TileCache.zig");
const utilconversion = @import("./util/utilconversion.zig");
const utilmepolang = @import("util/utilmepolang.zig");
const utilplatform = @import("util/utilplatform.zig");
const utilprefs = @import("util/utilprefs.zig");
const utildbg = @import("util/utildbg.zig");
const utilsdl = @import("util/utilsdl.zig");
const zoom_relative = @import("./api/zoom_relative.zig").zoom_relative;


@@ 16,11 17,7 @@ var pending_signal: ?u6 = null;
allocator: std.mem.Allocator,
async_shellpipe_threads: std.ArrayList(*sdl.SDL_Thread),
config: []const u8,
crosshair_size: u8 = 10,
debug_message: ?[]const u8 = null,
debug_message_enabled: bool = false,
debug_tiles: bool = false,
distance_unit_tf_km_mi: bool = false,
drag: ?struct {
    begin_ticks: u32,
    point: sdl.SDL_Point,


@@ 29,16 26,11 @@ drag: ?struct {
} = null,
fingers: std.ArrayList(sdl.SDL_FingerID),
fingers_gesture_delta: isize = 0,
drag_scale: u8 = 1,
fonts_normal: [50]*sdl.TTF_Font,
fonts_bold: [50]*sdl.TTF_Font,
fontsize_ui: u8 = 20,
help: bool = false,
lat: f64 = 0,
lon: f64 = 0,
idle_mutex: std.Thread.Mutex,
overlay_debugbar: bool = true,
overlay_pindetails: bool = true,
pin_group_active: u8 = 0,
pin_group_active_item: ?u32 = null,
pin_groups: [10]types.PinGroup = undefined,


@@ 60,17 52,17 @@ fn blit_crosshair(mepo: *@This()) errors.SDLError!void {
    try utilsdl.sdl_renderer_set_draw_color(mepo.renderer, .{ .value = 0x000000 });
    try utilsdl.errorcheck(sdl.SDL_RenderDrawLine(
        mepo.renderer,
        @intCast(c_int, mepo.win_w / 2 - (mepo.crosshair_size / 2)),
        @intCast(c_int, mepo.win_w / 2 - (utilprefs.get("crosshair_size").u / 2)),
        @intCast(c_int, mepo.win_h / 2),
        @intCast(c_int, mepo.win_w / 2 + (mepo.crosshair_size / 2)),
        @intCast(c_int, mepo.win_w / 2 + (utilprefs.get("crosshair_size").u / 2)),
        @intCast(c_int, mepo.win_h / 2),
    ));
    try utilsdl.errorcheck(sdl.SDL_RenderDrawLine(
        mepo.renderer,
        @intCast(c_int, mepo.win_w / 2),
        @intCast(c_int, mepo.win_h / 2 - (mepo.crosshair_size / 2)),
        @intCast(c_int, mepo.win_h / 2 - (utilprefs.get("crosshair_size").u / 2)),
        @intCast(c_int, mepo.win_w / 2),
        @intCast(c_int, mepo.win_h / 2 + (mepo.crosshair_size / 2)),
        @intCast(c_int, mepo.win_h / 2 + (utilprefs.get("crosshair_size").u / 2)),
    ));
}



@@ 110,7 102,7 @@ pub fn blit_tiles_all(mepo: *@This(), bbox: sdl.SDL_Rect, zoom: u8) !void {
}

pub fn blit_tile_text(mepo: *@This(), tile_x: u32, tile_y: u32, zoom: u8, x_off: i32, y_off: i32) !void {
    if (!mepo.debug_tiles) return;
    if (!utilprefs.get("debug_tiles").b) return;
    var dl_now: c_long = 0;
    var dl_total: c_long = 0;



@@ 199,9 191,10 @@ pub fn blit_tile_surface(mepo: *@This(), tile_x: u32, tile_y: u32, zoom: u8, x_o
}

fn blit_overlay_pindetails(mepo: *@This()) !void {
    if (!mepo.overlay_pindetails) return;
    if (!utilprefs.get("overlay_pindetails").b) return;

    const d_unit = if (mepo.distance_unit_tf_km_mi) "km" else "mi";
    const d_unit_km = utilprefs.get("distance_unit_tf_km_mi").b;
    const d_unit = if (d_unit_km) "km" else "mi";

    var arena = std.heap.ArenaAllocator.init(mepo.allocator);
    defer arena.deinit();


@@ 212,7 205,7 @@ fn blit_overlay_pindetails(mepo: *@This()) !void {
        const pin_coords_str = try std.fmt.allocPrintZ(arena.allocator(), "{d:.5} lat / {d:.5} lon", .{ pin.lat, pin.lon });
        const distance_str = try std.fmt.allocPrintZ(arena.allocator(), "{d:.2}{s} away", .{
            utilconversion.distance_haversine(
                if (mepo.distance_unit_tf_km_mi) .Km else .Mi,
                if (d_unit_km) .Km else .Mi,
                mepo.lat,
                mepo.lon,
                pin.lat,


@@ 248,8 241,8 @@ fn blit_overlay_pindetails(mepo: *@This()) !void {
}

fn blit_debugmessage(mepo: *@This()) !void {
    if (mepo.debug_message_enabled and mepo.debug_message != null) {
        const bottom_off: u32 = if (mepo.overlay_debugbar) mepo.fontsize_ui + 10 else 5;
    if (utilprefs.get("debug_message_enabled").b and mepo.debug_message != null) {
        const bottom_off: u32 = if (utilprefs.get("overlay_debugbar").b) utilprefs.get("fontsize_ui").u + 10 else 5;
        try mepo.blit_multiline_text(
            0xff0000,
            null,


@@ 355,8 348,8 @@ fn blit_pins(mepo: *@This()) !void {
}

fn blit_overlay_debugbar(mepo: *@This()) !void {
    if (!mepo.overlay_debugbar) return;
    const bottombar_height = mepo.fontsize_ui + 5;
    if (!utilprefs.get("overlay_debugbar").b) return;
    const bottombar_height = utilprefs.get("fontsize_ui").u + 5;

    const bg: types.Color = color: {
        if (mepo.tile_cache.thread_download == null) {


@@ 416,7 409,7 @@ fn blit_overlay_debugbar(mepo: *@This()) !void {
}

fn blit_help(mepo: *@This()) !void {
    if (!mepo.help) return;
    if (!utilprefs.get("help").b) return;

    var msg = msg: {
        var acc = std.ArrayList([]const u8).init(mepo.allocator);


@@ 462,7 455,7 @@ fn blit_uibuttons(mepo: *@This(), determine_click_target: ?sdl.SDL_MouseButtonEv
    var btn_i = mepo.uibuttons.items.len;
    const pad: c_int = 5;
    var right_pad: c_int = 10;
    const pad_bottom: c_int = mepo.fontsize_ui + 10;
    const pad_bottom: c_int = utilprefs.get("fontsize_ui").u + 10;

    while (btn_i > 0) {
        btn_i -= 1;


@@ 472,7 465,7 @@ fn blit_uibuttons(mepo: *@This(), determine_click_target: ?sdl.SDL_MouseButtonEv

        const surf = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderText_Blended(mepo.fonts_bold[mepo.fontsize_ui], @ptrCast([*c]const u8, mepo.uibuttons.items[btn_i].text), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderText_Blended(mepo.fonts_bold[utilprefs.get("fontsize_ui").u], @ptrCast([*c]const u8, mepo.uibuttons.items[btn_i].text), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        defer sdl.SDL_FreeSurface(surf);



@@ 719,8 712,8 @@ fn event_mousemotion(mepo: *@This(), e: sdl.SDL_Event) types.Pending {
        mepo.drag.?.point.y = e.motion.y;
        mepo.drag.?.delta_x += std.math.absInt(e.motion.xrel) catch unreachable;
        mepo.drag.?.delta_y += std.math.absInt(e.motion.yrel) catch unreachable;
        mepo.set_x(mepo.get_x() - (e.motion.xrel * mepo.drag_scale));
        mepo.set_y(mepo.get_y() - (e.motion.yrel * mepo.drag_scale));
        mepo.set_x(mepo.get_x() - (e.motion.xrel * utilprefs.get("drag_scale").u));
        mepo.set_y(mepo.get_y() - (e.motion.yrel * utilprefs.get("drag_scale").u));
        return .Drag;
    } else {
        mepo.drag = null;


@@ 1016,12 1009,12 @@ fn blit_table(mepo: *@This(), x: i32, y: i32, padding: c_int, rows: []const [2][
    for (rows) |row| {
        const surf_lab = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderText_Blended(mepo.fonts_bold[mepo.fontsize_ui], @ptrCast([*c]const u8, row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderText_Blended(mepo.fonts_bold[utilprefs.get("fontsize_ui").u], @ptrCast([*c]const u8, row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        defer sdl.SDL_FreeSurface(surf_lab);
        const surf_val = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderText_Blended(mepo.fonts_normal[mepo.fontsize_ui], @ptrCast([*c]const u8, row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderText_Blended(mepo.fonts_normal[utilprefs.get("fontsize_ui").u], @ptrCast([*c]const u8, row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        defer sdl.SDL_FreeSurface(surf_val);
        width_label = std.math.max(width_label, surf_lab.w);


@@ 1055,7 1048,7 @@ fn blit_table(mepo: *@This(), x: i32, y: i32, padding: c_int, rows: []const [2][
        // Label
        const surf_lab = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderText_Blended(mepo.fonts_bold[mepo.fontsize_ui], @ptrCast([*c]const u8, row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderText_Blended(mepo.fonts_bold[utilprefs.get("fontsize_ui").u], @ptrCast([*c]const u8, row[0]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        const text_lab = try utilsdl.errorcheck_ptr(sdl.SDL_Texture, sdl.SDL_CreateTextureFromSurface(mepo.renderer, surf_lab));
        defer sdl.SDL_FreeSurface(surf_lab);


@@ 1070,7 1063,7 @@ fn blit_table(mepo: *@This(), x: i32, y: i32, padding: c_int, rows: []const [2][
        // Value
        const surf_value = try utilsdl.errorcheck_ptr(
            sdl.SDL_Surface,
            sdl.TTF_RenderText_Blended(mepo.fonts_normal[mepo.fontsize_ui], @ptrCast([*c]const u8, row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
            sdl.TTF_RenderText_Blended(mepo.fonts_normal[utilprefs.get("fontsize_ui").u], @ptrCast([*c]const u8, row[1]), (types.Color{ .value = 0x000000 }).to_sdl()),
        );
        const text_value = try utilsdl.errorcheck_ptr(sdl.SDL_Texture, sdl.SDL_CreateTextureFromSurface(mepo.renderer, surf_value));
        defer sdl.SDL_FreeSurface(surf_value);


@@ 1111,7 1104,7 @@ pub fn blit_multiline_text(
        if (font_size_opt) |font_size| {
            if (font_size > mepo.fonts_normal.len) return error.FontTooBig else break :font_size font_size;
        } else {
            break :font_size mepo.fontsize_ui;
            break :font_size utilprefs.get("fontsize_ui").u;
        }
    };


M src/TileCache.zig => src/TileCache.zig +14 -16
@@ 12,6 12,7 @@ const utilsdl = @import("./util/utilsdl.zig");
const utilconversion = @import("./util/utilconversion.zig");
const utilfile = @import("./util/utilfile.zig");
const utildbg = @import("./util/utildbg.zig");
const utilprefs = @import("./util/utilprefs.zig");
const datastructure = @import("./datastructure/datastructure.zig");

pub const DownloadBBoxRequest = struct {


@@ 46,12 47,9 @@ bbox_queue: ?DownloadBBoxRequest = null,
byte_counter: u64 = 0,
cache_dir: ?std.fs.Dir = null,
curl_multi: *curl.CURLM,
expiry_seconds: i32 = -1,
max_n_transfers: u8 = 20,
queue_lifo_ui: datastructure.QueueHashMap(types.XYZ, void),
queue_lifo_bg: datastructure.QueueHashMap(types.XYZ, void),
renderer: ?*sdl.SDL_Renderer = null,
source_url: [:0]const u8 = undefined,
surface_map: datastructure.QueueHashMap(types.XYZ, *sdl.SDL_Surface),
texture_map: datastructure.EvictionHashMap(types.XYZ, *sdl.SDL_Texture, config.MaxTextures),
transfer_map: datastructure.QueueHashMap(types.XYZ, *TransferDatum),


@@ 83,13 81,13 @@ pub fn download_loop(tile_cache: *@This(), graphical_mode: bool) !void {
        }

        // 2. Transfer from UI LIFO into transfers
        while (tile_cache.queue_lifo_ui.count() > 0 and tile_cache.transfer_map.count() < tile_cache.max_n_transfers) {
        while (tile_cache.queue_lifo_ui.count() > 0 and tile_cache.transfer_map.count() < utilprefs.get("tile_cache_max_n_transfers").u) {
            var coords = tile_cache.queue_lifo_ui.pop();
            try tile_cache.curl_add_to_multi_and_register_transfer(coords.key, true);
        }

        // 3. Transfer from BG LIFO into transfers
        while (tile_cache.queue_lifo_bg.count() > 0 and tile_cache.transfer_map.count() < tile_cache.max_n_transfers) {
        while (tile_cache.queue_lifo_bg.count() > 0 and tile_cache.transfer_map.count() < utilprefs.get("tile_cache_max_n_transfers").u) {
            var coords = tile_cache.queue_lifo_bg.pop();
            try tile_cache.curl_add_to_multi_and_register_transfer(coords.key, false);
        }


@@ 135,13 133,13 @@ pub fn set_cache_url(tile_cache: *@This(), url: [:0]const u8) !void {
    tile_cache.queue_lifo_ui.clearAndFree();
    tile_cache.queue_lifo_bg.clearAndFree();

    // Set new URL
    tile_cache.allocator.free(tile_cache.source_url);
    tile_cache.source_url = try tile_cache.allocator.dupeZ(u8, url);
    try utilprefs.set_t(tile_cache.allocator, "tile_cache_url", url);
}

pub fn set_cache_dir(tile_cache: *@This(), path: [:0]const u8) !void {
    const expanded_path = try utilfile.wordexp_filepath(tile_cache.allocator, path);
    try utilprefs.set_t(tile_cache.allocator, "tile_cache_dir", path);

    const expanded_path = try utilfile.wordexp_filepath(tile_cache.allocator, utilprefs.get("tile_cache_dir").t.?);
    defer tile_cache.allocator.free(expanded_path);
    try std.fs.cwd().makePath(expanded_path);
    tile_cache.cache_dir = try std.fs.cwd().openDir(expanded_path, .{ .iterate = true });


@@ 256,7 254,7 @@ pub fn tile_ui_retreive_or_queue(tile_cache: *@This(), coords: types.XYZ) !TileD

        // Load tile to surface
        {
            const png = try png_path(tile_cache.allocator, tile_cache.source_url, coords);
            const png = try png_path(tile_cache.allocator, utilprefs.get("tile_cache_url").t.?, coords);
            defer tile_cache.allocator.free(png);
            file_cached_png_opt = cache_dir.readFileAlloc(tile_cache.allocator, png, 500000) catch null;
            if (file_cached_png_opt) |file_cached_png| {


@@ 308,10 306,10 @@ fn curl_add_to_multi_and_register_transfer(tile_cache: *@This(), coords: types.X
    try tile_cache.transfer_map.put(coords, transfer_datum);

    var tile_url = url: {
        var url = try tile_cache.allocator.alloc(u8, tile_cache.source_url.len + (3 * 10));
        var url = try tile_cache.allocator.alloc(u8, utilprefs.get("tile_cache_url").t.?.len + (3 * 10));
        if (cstdio.sprintf(
            &url[0],
            tile_cache.source_url,
            utilprefs.get("tile_cache_url").t.?,
            @intCast(c_int, coords.x),
            @intCast(c_int, coords.y),
            @intCast(c_int, coords.z),


@@ 426,7 424,7 @@ fn download_loop_transfer_complete(tile_cache: *@This(), msg: *curl.CURLMsg) !vo

                if (tile_cache.cache_dir) |cache_dir| {
                    // Save to FS
                    const path = try png_path(tile_cache.allocator, tile_cache.source_url, coords);
                    const path = try png_path(tile_cache.allocator, utilprefs.get("tile_cache_url").t.?, coords);
                    try cache_dir.writeFile(path, datum_array);
                }
                if (tile_cache.transfer_map.get(coords).?.load_to_texture) {


@@ 482,11 480,12 @@ fn png_path(allocator: std.mem.Allocator, source: []const u8, coords: types.XYZ)
/// to creation time & expiry seconds setting
fn tile_exists_in_fs_and_non_expired(tile_cache: *@This(), coords: types.XYZ) !bool {
    if (tile_cache.cache_dir) |cache_dir| {
        const png = try png_path(tile_cache.allocator, tile_cache.source_url, coords);
        const png = try png_path(tile_cache.allocator, utilprefs.get("tile_cache_url").t.?, coords);
        defer tile_cache.allocator.free(png);
        const tile_file = cache_dir.openFile(png, .{}) catch return false;
        defer tile_file.close();
        if (tile_cache.expiry_seconds < 0 or std.time.timestamp() < (try tile_file.stat()).ctime + tile_cache.expiry_seconds) {
        const expiry_seconds = @floatToInt(i32, utilprefs.get("tile_cache_expiry_seconds").f);
        if (expiry_seconds < 0 or std.time.timestamp() < (try tile_file.stat()).ctime + expiry_seconds) {
            return true;
        }
    }


@@ 517,7 516,6 @@ pub fn init(allocator: std.mem.Allocator) anyerror!@This() {
        return @as(@This(), .{
            .allocator = allocator,
            .curl_multi = curl.curl_multi_init().?,
            .source_url = try allocator.dupeZ(u8, ""),
            .queue_lifo_ui = datastructure.QueueHashMap(types.XYZ, void).init(allocator),
            .queue_lifo_bg = datastructure.QueueHashMap(types.XYZ, void).init(allocator),
            .surface_map = datastructure.QueueHashMap(types.XYZ, *sdl.SDL_Surface).init(allocator),

M src/api/prefinc.zig => src/api/prefinc.zig +4 -16
@@ 2,6 2,7 @@ const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const utildbg = @import("../util/utildbg.zig");
const utilprefs = @import("../util/utilprefs.zig");

pub const spec = .{
    .name = "prefinc",


@@ 13,23 14,10 @@ pub const spec = .{
    .execute = execute,
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
fn execute(_: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const name = args[0].Text;
    const delta = args[1].Number;

    for (&[_]struct { prop: []const u8, ptr: *u8 }{
        .{ .prop = "fontsize_ui", .ptr = &mepo.fontsize_ui },
        .{ .prop = "drag_scale", .ptr = &mepo.drag_scale },
        .{ .prop = "zoom", .ptr = &mepo.zoom },
        .{ .prop = "crosshair_size", .ptr = &mepo.crosshair_size },
        .{ .prop = "tile_cache_max_n_transfers", .ptr = &mepo.tile_cache.max_n_transfers },
    }) |stg| {
        if (std.mem.eql(u8, stg.prop, name)) {
            if (delta > 0) {
                stg.ptr.* +|= @floatToInt(u8, delta);
            } else {
                stg.ptr.* -|= @intCast(u8, try std.math.absInt(@floatToInt(i8, delta)));
            }
        }
    }
    const val = utilprefs.get(name).u;
    utilprefs.set_n(name, @intToFloat(f64, val) + delta);
}

M src/api/prefset_n.zig => src/api/prefset_n.zig +2 -31
@@ 2,6 2,7 @@ const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const utildbg = @import("../util/utildbg.zig");
const utilprefs = @import("../util/utilprefs.zig");

pub const spec = .{
    .name = "prefset_n",


@@ 18,38 19,8 @@ fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const name = args[0].Text;
    const value = args[1].Number;

    // Bools
    for (&[_]struct { prop: []const u8, ptr: *bool }{
        .{ .prop = "debug_message_enabled", .ptr = &mepo.debug_message_enabled },
        .{ .prop = "debug_stderr", .ptr = &utildbg.debug_stderr },
        .{ .prop = "debug_tiles", .ptr = &mepo.debug_tiles },
        .{ .prop = "distance_unit_tf_km_mi", .ptr = &mepo.distance_unit_tf_km_mi },
        .{ .prop = "overlay_debugbar", .ptr = &mepo.overlay_debugbar },
        .{ .prop = "overlay_pindetails", .ptr = &mepo.overlay_pindetails },
        .{ .prop = "help", .ptr = &mepo.help },
    }) |stg| {
        if (std.mem.eql(u8, stg.prop, name)) stg.ptr.* = value == 1;
    }

    // U8 Ints
    for (&[_]struct { prop: []const u8, ptr: *u8 }{
        .{ .prop = "fontsize_ui", .ptr = &mepo.fontsize_ui },
        .{ .prop = "drag_scale", .ptr = &mepo.drag_scale },
        .{ .prop = "zoom", .ptr = &mepo.zoom },
        .{ .prop = "crosshair_size", .ptr = &mepo.crosshair_size },
        .{ .prop = "tile_cache_max_n_transfers", .ptr = &mepo.tile_cache.max_n_transfers },
    }) |stg| {
        if (std.mem.eql(u8, stg.prop, name)) stg.ptr.* = @floatToInt(u8, value);
    }

    // I32 Ints
    for (&[_]struct { prop: []const u8, ptr: *i32 }{
        .{ .prop = "tile_cache_expiry_seconds", .ptr = &mepo.tile_cache.expiry_seconds },
    }) |stg| {
        if (std.mem.eql(u8, stg.prop, name)) stg.ptr.* = @floatToInt(i32, value);
    }

    if (std.mem.eql(u8, "tile_cache_network", name)) {
        mepo.tile_cache.set_network(value == 1);
    }
    utilprefs.set_n(name, value);
}

M src/api/prefset_t.zig => src/api/prefset_t.zig +3 -1
@@ 2,6 2,7 @@ const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const utildbg = @import("../util/utildbg.zig");
const utilprefs = @import("../util/utilprefs.zig");

pub const spec = .{
    .name = "prefset_t",


@@ 18,7 19,8 @@ fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const name = args[0].Text;
    const value = args[1].Text;

    // Tilecache
    // Under the hood uses utilprefs.set_t
    // but unlike numerical updates, these generally need 'syncing' fns
    if (std.mem.eql(u8, "tile_cache_dir", name)) {
        try mepo.tile_cache.set_cache_dir(value);
    } else if (std.mem.eql(u8, "tile_cache_url", name)) {

M src/api/preftoggle.zig => src/api/preftoggle.zig +6 -12
@@ 2,6 2,7 @@ const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const utildbg = @import("../util/utildbg.zig");
const utilprefs = @import("../util/utilprefs.zig");

pub const spec = .{
    .name = "preftoggle",


@@ 16,18 17,11 @@ pub const spec = .{
fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const name = args[0].Text;

    for (&[_]struct { prop: []const u8, ptr: *bool }{
        .{ .prop = "help", .ptr = &mepo.help },
        .{ .prop = "overlay_debugbar", .ptr = &mepo.overlay_debugbar },
        .{ .prop = "overlay_pindetails", .ptr = &mepo.overlay_pindetails },
        .{ .prop = "debug_message_enabled", .ptr = &mepo.debug_message_enabled },
        .{ .prop = "debug_tiles", .ptr = &mepo.debug_tiles },
        .{ .prop = "debug_stderr", .ptr = &utildbg.debug_stderr },
    }) |stg| {
        if (std.mem.eql(u8, stg.prop, name)) stg.ptr.* = !stg.ptr.*;
    }

    if (std.mem.eql(u8, "tile_cache_network", name)) {
        mepo.tile_cache.set_network(!(mepo.tile_cache.thread_download != null));
        const network_on = !(mepo.tile_cache.thread_download != null);
        mepo.tile_cache.set_network(network_on);
        utilprefs.set_n(name, if (network_on) 1 else 0);
    } else {
        utilprefs.toggle_bool(name);
    }
}

M src/util/utildbg.zig => src/util/utildbg.zig +2 -3
@@ 1,7 1,6 @@
const std = @import("std");

pub var debug_stderr: bool = true;
const utilprefs = @import("./utilprefs.zig");

pub fn log(comptime msg: []const u8, args: anytype) void {
    if (debug_stderr) std.debug.print(msg, args);
    if (utilprefs.get("debug_stderr").b) std.debug.print(msg, args);
}

A src/util/utilprefs.zig => src/util/utilprefs.zig +157 -0
@@ 0,0 1,157 @@
const std = @import("std");

const PrefValueEnum = enum { b, t, f, u };
const PrefValueUnion = union(PrefValueEnum) { b: bool, t: ?[:0]const u8, f: f64, u: u8 };
const PrefMapping = struct {
    name: []const u8,
    value: PrefValueUnion,
    desc: []const u8,
};

const n_prefs = 16;
var prefs_mapping: [n_prefs]PrefMapping = mapping: {
    var mapping: [n_prefs]PrefMapping = undefined;
    mapping[0] = .{
        .name = "debug_message_enabled",
        .value = .{ .b = true },
        .desc = "TODO",
    };
    mapping[1] = .{
        .name = "distance_unit_tf_km_mi",
        .value = .{ .b = false },
        .desc = "TODO",
    };
    mapping[2] = .{
        .name = "overlay_debugbar",
        .value = .{ .b = true },
        .desc = "TODO",
    };
    mapping[3] = .{
        .name = "help",
        .value = .{ .b = false },
        .desc = "TODO",
    };
    mapping[4] = .{
        .name = "overlay_pindetails",
        .value = .{ .b = true },
        .desc = "TODO",
    };
    mapping[5] = .{
        .name = "crosshair_size",
        .value = .{ .u = 15 },
        .desc = "TODO",
    };
    mapping[6] = .{
        .name = "drag_scale",
        .value = .{ .u = 2 },
        .desc = "TODO",
    };
    mapping[7] = .{
        .name = "fontsize_ui",
        .value = .{ .u = 20 },
        .desc = "TODO",
    };
    mapping[8] = .{
        .name = "zoom",
        .value = .{ .u = 14 },
        .desc = "TODO",
    };
    mapping[9] = .{
        .name = "debug_tiles",
        .value = .{ .b = false },
        .desc = "TODO",
    };
    mapping[10] = .{
        .name = "debug_stderr",
        .value = .{ .b = false },
        .desc = "TODO",
    };
    mapping[11] = .{
        .name = "tile_cache_max_n_transfers",
        .value = .{ .u = 20 },
        .desc = "TODO",
    };
    mapping[12] = .{
        .name = "tile_cache_expiry_seconds",
        .value = .{ .f = -1 },
        .desc = "TODO",
    };
    mapping[13] = .{
        .name = "tile_cache_network",
        .value = .{ .b = true },
        .desc = "TODO",
    };
    mapping[14] = .{
        .name = "tile_cache_dir",
        .value = .{ .t = null },
        .desc = "TODO",
    };
    mapping[15] = .{
        .name = "tile_cache_url",
        .value = .{ .t = null },
        .desc = "TODO",
    };
    break :mapping mapping;
};

pub fn get_u8(prefname: []const u8) f64 {
    for (prefs_mapping) |pref| {
        if (std.mem.eql(u8, pref.name, prefname)) return pref.value.u;
    }
    return 0;
}

pub fn get(prefname: []const u8) PrefValueUnion {
    for (prefs_mapping) |pref| {
        if (std.mem.eql(u8, pref.name, prefname)) {
            return pref.value;
        }
    }
    return .{ .b = false };
}

pub fn set_n(prefname: []const u8, value: f64) void {
    for (prefs_mapping) |*pref| {
        if (std.mem.eql(u8, pref.name, prefname)) {
            switch (pref.value) {
                .b => {
                    pref.value = .{ .b = value == 1 };
                },
                .u => {
                    pref.value = .{ .u = @floatToInt(u8, value) };
                },
                .f => {
                    pref.value = .{ .f = value };
                },
                .t => {},
            }
        }
    }
}

pub fn set_t(allocator: std.mem.Allocator, prefname: []const u8, value: []const u8) !void {
    for (prefs_mapping) |*pref| {
        if (std.mem.eql(u8, pref.name, prefname)) {
            switch (pref.value) {
                .t => {
                    if (pref.value.t) |heap_allocated_text| allocator.free(heap_allocated_text);
                    pref.value.t = try allocator.dupeZ(u8, value);
                },
                else => {}
            }
        }
    }
}

pub fn toggle_bool(prefname: []const u8) void {
    for (prefs_mapping) |*pref| {
        if (std.mem.eql(u8, pref.name, prefname)) {
            switch (pref.value) {
                .b => {
                    pref.value = .{ .b = !pref.value.b };
                },
                else => {},
            }
        }
    }
}