lib/zen/src/document/capacity.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const model = @import("model.zig");
3 const plan_mod = @import("plan.zig");
4
5 pub const storage_alignment: usize = @alignOf(model.Heading);
6
7 pub const Limits = plan_mod.Limits;
8 pub const DeriveError = error{CapacityOverflow};
9
10 pub const Capacity = struct {
11 limits: Limits,
12 headings_offset: usize,
13 heading_bytes: usize,
14 text_offset: usize,
15 id_offset: usize,
16 storage_bytes: usize,
17
18 pub fn derive(limits: Limits) DeriveError!Capacity {
19 const heading_bytes = std.math.mul(
20 usize,
21 limits.max_headings,
22 @sizeOf(model.Heading),
23 ) catch return error.CapacityOverflow;
24 const id_offset = try added(heading_bytes, limits.max_text_bytes);
25 return .{
26 .limits = limits,
27 .headings_offset = 0,
28 .heading_bytes = heading_bytes,
29 .text_offset = heading_bytes,
30 .id_offset = id_offset,
31 .storage_bytes = try added(id_offset, limits.max_id_bytes),
32 };
33 }
34 };
35
36 fn added(left: usize, right: usize) DeriveError!usize {
37 return std.math.add(usize, left, right) catch error.CapacityOverflow;
38 }
39
40 fn independent(limits: Limits) DeriveError!Capacity {
41 const heading_bytes = @as(u128, limits.max_headings) * @sizeOf(model.Heading);
42 const id_offset = heading_bytes + limits.max_text_bytes;
43 const storage_bytes = id_offset + limits.max_id_bytes;
44 if (heading_bytes > std.math.maxInt(usize) or
45 id_offset > std.math.maxInt(usize) or
46 storage_bytes > std.math.maxInt(usize))
47 {
48 return error.CapacityOverflow;
49 }
50 return .{
51 .limits = limits,
52 .headings_offset = 0,
53 .heading_bytes = @intCast(heading_bytes),
54 .text_offset = @intCast(heading_bytes),
55 .id_offset = @intCast(id_offset),
56 .storage_bytes = @intCast(storage_bytes),
57 };
58 }
59
60 test "heading capacity matches an independent byte model" {
61 comptime {
62 @stardustClaim(
63 @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "zen_heading_capacity"),
64 null,
65 null,
66 null,
67 null,
68 null,
69 null,
70 );
71 }
72
73 const limits = Limits{
74 .max_headings = 2,
75 .max_text_bytes = 11,
76 .max_id_bytes = 11,
77 };
78 const capacity = try Capacity.derive(limits);
79 try std.testing.expectEqual(try independent(limits), capacity);
80 try std.testing.expectEqual(@as(usize, 118), capacity.storage_bytes);
81 try std.testing.expectEqual(
82 @as(usize, 2_293_760),
83 (try Capacity.derive(plan_mod.default_limits)).storage_bytes,
84 );
85 }
86
87 test "heading capacity handles zero and rejects overflow" {
88 try std.testing.expectEqual(@as(usize, 0), (try Capacity.derive(.{
89 .max_headings = 0,
90 .max_text_bytes = 0,
91 .max_id_bytes = 0,
92 })).storage_bytes);
93 try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
94 .max_headings = std.math.maxInt(usize),
95 .max_text_bytes = 0,
96 .max_id_bytes = 0,
97 }));
98 try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
99 .max_headings = 0,
100 .max_text_bytes = std.math.maxInt(usize),
101 .max_id_bytes = 1,
102 }));
103 }