lib/zen/src/diagram/ascii/capacity.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const diagram = @import("../root.zig");
4 const plan = @import("plan.zig");
5
6 pub const storage_alignment: usize = @max(
7 @alignOf([]const u8),
8 @alignOf(diagram.bounds.StackTotal),
9 );
10
11 pub const Limits = plan.Limits;
12 pub const DeriveError = error{CapacityOverflow};
13
14 pub const Capacity = struct {
15 limits: Limits,
16 categories_offset: usize,
17 category_bytes: usize,
18 stack_totals_offset: usize,
19 stack_total_bytes: usize,
20 canvas_offset: usize,
21 output_offset: usize,
22 storage_bytes: usize,
23
24 pub fn derive(limits: Limits) DeriveError!Capacity {
25 const category_bytes = try multiplied(limits.max_categories, @sizeOf([]const u8));
26 const stack_totals_offset = try aligned(category_bytes, @alignOf(diagram.bounds.StackTotal));
27 const stack_total_bytes = try multiplied(
28 limits.max_stack_totals,
29 @sizeOf(diagram.bounds.StackTotal),
30 );
31 const canvas_offset = try added(stack_totals_offset, stack_total_bytes);
32 const output_offset = try added(canvas_offset, limits.max_canvas_bytes);
33 return .{
34 .limits = limits,
35 .categories_offset = 0,
36 .category_bytes = category_bytes,
37 .stack_totals_offset = stack_totals_offset,
38 .stack_total_bytes = stack_total_bytes,
39 .canvas_offset = canvas_offset,
40 .output_offset = output_offset,
41 .storage_bytes = try added(output_offset, limits.max_output_bytes),
42 };
43 }
44 };
45
46 fn added(left: usize, right: usize) DeriveError!usize {
47 return std.math.add(usize, left, right) catch error.CapacityOverflow;
48 }
49
50 fn multiplied(left: usize, right: usize) DeriveError!usize {
51 return std.math.mul(usize, left, right) catch error.CapacityOverflow;
52 }
53
54 fn aligned(value: usize, alignment: usize) DeriveError!usize {
55 const padding = (alignment - value % alignment) % alignment;
56 return added(value, padding);
57 }
58
59 fn independent(limits: Limits) DeriveError!Capacity {
60 const category_bytes = @as(u128, limits.max_categories) * @sizeOf([]const u8);
61 const alignment = @alignOf(diagram.bounds.StackTotal);
62 const stack_totals_offset = category_bytes + (alignment - category_bytes % alignment) % alignment;
63 const stack_total_bytes = @as(u128, limits.max_stack_totals) * @sizeOf(diagram.bounds.StackTotal);
64 const canvas_offset = stack_totals_offset + stack_total_bytes;
65 const output_offset = canvas_offset + limits.max_canvas_bytes;
66 const storage_bytes = output_offset + limits.max_output_bytes;
67 if (category_bytes > std.math.maxInt(usize) or
68 stack_totals_offset > std.math.maxInt(usize) or
69 stack_total_bytes > std.math.maxInt(usize) or
70 canvas_offset > std.math.maxInt(usize) or
71 output_offset > std.math.maxInt(usize) or
72 storage_bytes > std.math.maxInt(usize))
73 {
74 return error.CapacityOverflow;
75 }
76 return .{
77 .limits = limits,
78 .categories_offset = 0,
79 .category_bytes = @intCast(category_bytes),
80 .stack_totals_offset = @intCast(stack_totals_offset),
81 .stack_total_bytes = @intCast(stack_total_bytes),
82 .canvas_offset = @intCast(canvas_offset),
83 .output_offset = @intCast(output_offset),
84 .storage_bytes = @intCast(storage_bytes),
85 };
86 }
87
88 test "ASCII render capacity matches an independent byte model" {
89 comptime {
90 @stardustClaim(
91 @import("alloc_phase").capacity.witness(@import("./root.zig").RenderStorage, "zen_ascii_render_capacity"),
92 null,
93 null,
94 null,
95 null,
96 null,
97 null,
98 );
99 }
100
101 const limits = Limits{
102 .max_categories = 3,
103 .max_stack_totals = 2,
104 .max_canvas_bytes = 72 * 22,
105 .max_output_bytes = 73 * 22,
106 };
107 const capacity = try Capacity.derive(limits);
108 try std.testing.expectEqual(try independent(limits), capacity);
109 try std.testing.expectEqual(
110 @as(usize, 3 * @sizeOf([]const u8) + 2 * @sizeOf(diagram.bounds.StackTotal) + 72 * 22 + 73 * 22),
111 capacity.storage_bytes,
112 );
113 }
114
115 test "ASCII render capacity handles zero and rejects overflow" {
116 try std.testing.expectEqual(@as(usize, 0), (try Capacity.derive(.{
117 .max_categories = 0,
118 .max_stack_totals = 0,
119 .max_canvas_bytes = 0,
120 .max_output_bytes = 0,
121 })).storage_bytes);
122 try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
123 .max_categories = std.math.maxInt(usize),
124 .max_stack_totals = 0,
125 .max_canvas_bytes = 0,
126 .max_output_bytes = 0,
127 }));
128 try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
129 .max_categories = 0,
130 .max_stack_totals = 0,
131 .max_canvas_bytes = std.math.maxInt(usize),
132 .max_output_bytes = 1,
133 }));
134 }