a73x

src/scale_tracker.zig

Ref:   Size: 3.8 KiB

const std = @import("std");

pub const OutputId = u32;

pub const ScaleTracker = struct {
    alloc: std.mem.Allocator,
    scales: std.AutoHashMapUnmanaged(OutputId, i32),
    entered: std.AutoHashMapUnmanaged(OutputId, void),

    pub fn init(alloc: std.mem.Allocator) ScaleTracker {
        return .{
            .alloc = alloc,
            .scales = .empty,
            .entered = .empty,
        };
    }

    pub fn deinit(self: *ScaleTracker) void {
        self.scales.deinit(self.alloc);
        self.entered.deinit(self.alloc);
    }

    pub fn addOutput(self: *ScaleTracker, id: OutputId) !void {
        try self.scales.put(self.alloc, id, 1);
    }

    pub fn setOutputScale(self: *ScaleTracker, id: OutputId, scale: i32) void {
        if (self.scales.getPtr(id)) |slot| slot.* = scale;
    }

    pub fn removeOutput(self: *ScaleTracker, id: OutputId) void {
        _ = self.scales.remove(id);
        _ = self.entered.remove(id);
    }

    pub fn enterOutput(self: *ScaleTracker, id: OutputId) !void {
        try self.entered.put(self.alloc, id, {});
    }

    pub fn leaveOutput(self: *ScaleTracker, id: OutputId) void {
        _ = self.entered.remove(id);
    }

    pub fn bufferScale(self: *const ScaleTracker) i32 {
        var max_scale: i32 = 1;
        var it = self.entered.iterator();
        while (it.next()) |entry| {
            const id = entry.key_ptr.*;
            if (self.scales.get(id)) |s| {
                if (s > max_scale) max_scale = s;
            }
        }
        return max_scale;
    }
};

test "new tracker reports default scale of 1" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();
    try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
}

test "entered output scale is reflected in bufferScale" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();

    try t.addOutput(1);
    t.setOutputScale(1, 2);
    try t.enterOutput(1);
    try std.testing.expectEqual(@as(i32, 2), t.bufferScale());
}

test "not-yet-entered output does not change bufferScale" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();

    try t.addOutput(7);
    t.setOutputScale(7, 3);
    try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
}

test "bufferScale is max across entered outputs" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();

    try t.addOutput(1);
    try t.addOutput(2);
    t.setOutputScale(1, 1);
    t.setOutputScale(2, 2);

    try t.enterOutput(1);
    try t.enterOutput(2);
    try std.testing.expectEqual(@as(i32, 2), t.bufferScale());
}

test "leaving an output drops its contribution" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();

    try t.addOutput(1);
    try t.addOutput(2);
    t.setOutputScale(1, 2);
    t.setOutputScale(2, 3);
    try t.enterOutput(1);
    try t.enterOutput(2);
    try std.testing.expectEqual(@as(i32, 3), t.bufferScale());

    t.leaveOutput(2);
    try std.testing.expectEqual(@as(i32, 2), t.bufferScale());
}

test "removing an unknown output is a no-op" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();
    t.removeOutput(999);
    try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
}

test "removeOutput also removes it from entered set" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();

    try t.addOutput(5);
    t.setOutputScale(5, 4);
    try t.enterOutput(5);
    try std.testing.expectEqual(@as(i32, 4), t.bufferScale());

    t.removeOutput(5);
    try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
}

test "setOutputScale on unknown id is a no-op" {
    var t = ScaleTracker.init(std.testing.allocator);
    defer t.deinit();
    t.setOutputScale(999, 5);
    try std.testing.expectEqual(@as(i32, 1), t.bufferScale());
}