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());
}