lib/gui/src/surface/capacity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const gui = @import("../root.zig");
  3 
  4 const UiNode = gui.model.UiNode;
  5 const UiSurfaceTree = gui.model.UiSurfaceTree;
  6 const UiTextRun = gui.model.UiTextRun;
  7 const UiTextStyle = gui.model.UiTextStyle;
  8 const UiImage = gui.model.UiImage;
  9 
 10 pub const Facts = struct {
 11     nodes: usize = 0,
 12     runs: usize = 0,
 13     styles: usize = 0,
 14     images: usize = 0,
 15     image_pixels: usize = 0,
 16     semantic_bytes: usize = 0,
 17 };
 18 
 19 pub const Limits = struct {
 20     nodes: usize,
 21     runs: usize = 0,
 22     styles: usize = 0,
 23     images: usize = 0,
 24     image_pixels: usize = 0,
 25     semantic_bytes: usize,
 26 
 27     pub fn admits(self: Limits, facts: Facts) bool {
 28         return facts.nodes <= self.nodes and
 29             facts.runs <= self.runs and
 30             facts.styles <= self.styles and
 31             facts.images <= self.images and
 32             facts.image_pixels <= self.image_pixels and
 33             facts.semantic_bytes <= self.semantic_bytes;
 34     }
 35 };
 36 
 37 pub const DeriveError = error{
 38     InvalidNodeLimit,
 39     CapacityOverflow,
 40 };
 41 
 42 pub const storage_alignment: usize = @max(
 43     @alignOf(UiSurfaceTree),
 44     @max(
 45         @alignOf(UiNode),
 46         @max(@alignOf(UiTextRun), @max(@alignOf(UiTextStyle), @alignOf(UiImage))),
 47     ),
 48 );
 49 
 50 pub const Capacity = struct {
 51     nodes: usize,
 52     runs: usize,
 53     styles: usize,
 54     images: usize,
 55     image_pixels: usize,
 56     semantic_bytes: usize,
 57     surface_offset: usize,
 58     surface_bytes: usize,
 59     node_offset: usize,
 60     node_slots: usize,
 61     node_bytes: usize,
 62     run_offset: usize,
 63     run_slots: usize,
 64     run_bytes: usize,
 65     style_offset: usize,
 66     style_slots: usize,
 67     style_bytes: usize,
 68     image_offset: usize,
 69     image_slots: usize,
 70     image_bytes: usize,
 71     image_pixel_offset: usize,
 72     image_pixel_slots: usize,
 73     image_pixel_bytes: usize,
 74     semantic_offset: usize,
 75     storage_bytes: usize,
 76 
 77     pub fn derive(limits: Limits) DeriveError!Capacity {
 78         if (limits.nodes == 0) return error.InvalidNodeLimit;
 79         const surface = try placed(UiSurfaceTree, 0, 1);
 80         const nodes = try placed(UiNode, surface.end, limits.nodes - 1);
 81         const runs = try placed(UiTextRun, nodes.end, limits.runs);
 82         const styles = try placed(UiTextStyle, runs.end, limits.styles);
 83         const images = try placed(UiImage, styles.end, limits.images);
 84         const image_pixels = try placed(u32, images.end, limits.image_pixels);
 85         const semantics = try placed(u8, image_pixels.end, limits.semantic_bytes);
 86         return .{
 87             .nodes = limits.nodes,
 88             .runs = limits.runs,
 89             .styles = limits.styles,
 90             .images = limits.images,
 91             .image_pixels = limits.image_pixels,
 92             .semantic_bytes = limits.semantic_bytes,
 93             .surface_offset = surface.start,
 94             .surface_bytes = surface.bytes,
 95             .node_offset = nodes.start,
 96             .node_slots = limits.nodes - 1,
 97             .node_bytes = nodes.bytes,
 98             .run_offset = runs.start,
 99             .run_slots = limits.runs,
100             .run_bytes = runs.bytes,
101             .style_offset = styles.start,
102             .style_slots = limits.styles,
103             .style_bytes = styles.bytes,
104             .image_offset = images.start,
105             .image_slots = limits.images,
106             .image_bytes = images.bytes,
107             .image_pixel_offset = image_pixels.start,
108             .image_pixel_slots = limits.image_pixels,
109             .image_pixel_bytes = image_pixels.bytes,
110             .semantic_offset = semantics.start,
111             .storage_bytes = semantics.end,
112         };
113     }
114 
115     pub fn admits(self: Capacity, facts: Facts) bool {
116         return self.toLimits().admits(facts);
117     }
118 
119     pub fn toLimits(self: Capacity) Limits {
120         return .{
121             .nodes = self.nodes,
122             .runs = self.runs,
123             .styles = self.styles,
124             .images = self.images,
125             .image_pixels = self.image_pixels,
126             .semantic_bytes = self.semantic_bytes,
127         };
128     }
129 };
130 
131 const Region = struct {
132     start: usize,
133     bytes: usize,
134     end: usize,
135 };
136 
137 fn aligned(offset: usize, alignment: usize) DeriveError!usize {
138     const mask = alignment - 1;
139     const padded = std.math.add(usize, offset, mask) catch return error.CapacityOverflow;
140     return padded & ~mask;
141 }
142 
143 fn placed(comptime T: type, offset: usize, count: usize) DeriveError!Region {
144     const start = try aligned(offset, @alignOf(T));
145     const bytes = std.math.mul(usize, count, @sizeOf(T)) catch return error.CapacityOverflow;
146     const end = std.math.add(usize, start, bytes) catch return error.CapacityOverflow;
147     return .{ .start = start, .bytes = bytes, .end = end };
148 }
149 
150 fn modelCapacity(limits: Limits) DeriveError!Capacity {
151     if (limits.nodes == 0) return error.InvalidNodeLimit;
152     const surface_offset = alignedModel(0, @alignOf(UiSurfaceTree));
153     const surface_bytes = @as(u128, @sizeOf(UiSurfaceTree));
154     const node_offset = alignedModel(surface_offset + surface_bytes, @alignOf(UiNode));
155     const node_slots = @as(u128, limits.nodes - 1);
156     const node_bytes = node_slots * @sizeOf(UiNode);
157     const run_offset = alignedModel(node_offset + node_bytes, @alignOf(UiTextRun));
158     const run_slots = @as(u128, limits.runs);
159     const run_bytes = run_slots * @sizeOf(UiTextRun);
160     const style_offset = alignedModel(run_offset + run_bytes, @alignOf(UiTextStyle));
161     const style_slots = @as(u128, limits.styles);
162     const style_bytes = style_slots * @sizeOf(UiTextStyle);
163     const image_offset = alignedModel(style_offset + style_bytes, @alignOf(UiImage));
164     const image_slots = @as(u128, limits.images);
165     const image_bytes = image_slots * @sizeOf(UiImage);
166     const image_pixel_offset = alignedModel(image_offset + image_bytes, @alignOf(u32));
167     const image_pixel_slots = @as(u128, limits.image_pixels);
168     const image_pixel_bytes = image_pixel_slots * @sizeOf(u32);
169     const semantic_offset = alignedModel(image_pixel_offset + image_pixel_bytes, @alignOf(u8));
170     const storage_bytes = semantic_offset + limits.semantic_bytes;
171     return .{
172         .nodes = limits.nodes,
173         .runs = limits.runs,
174         .styles = limits.styles,
175         .images = limits.images,
176         .image_pixels = limits.image_pixels,
177         .semantic_bytes = limits.semantic_bytes,
178         .surface_offset = try narrowed(surface_offset),
179         .surface_bytes = try narrowed(surface_bytes),
180         .node_offset = try narrowed(node_offset),
181         .node_slots = try narrowed(node_slots),
182         .node_bytes = try narrowed(node_bytes),
183         .run_offset = try narrowed(run_offset),
184         .run_slots = try narrowed(run_slots),
185         .run_bytes = try narrowed(run_bytes),
186         .style_offset = try narrowed(style_offset),
187         .style_slots = try narrowed(style_slots),
188         .style_bytes = try narrowed(style_bytes),
189         .image_offset = try narrowed(image_offset),
190         .image_slots = try narrowed(image_slots),
191         .image_bytes = try narrowed(image_bytes),
192         .image_pixel_offset = try narrowed(image_pixel_offset),
193         .image_pixel_slots = try narrowed(image_pixel_slots),
194         .image_pixel_bytes = try narrowed(image_pixel_bytes),
195         .semantic_offset = try narrowed(semantic_offset),
196         .storage_bytes = try narrowed(storage_bytes),
197     };
198 }
199 
200 fn alignedModel(offset: u128, alignment: usize) u128 {
201     const mask = @as(u128, alignment - 1);
202     return (offset + mask) & ~mask;
203 }
204 
205 fn narrowed(value: u128) DeriveError!usize {
206     if (value > std.math.maxInt(usize)) return error.CapacityOverflow;
207     return @intCast(value);
208 }
209 
210 test "surface clone capacity matches an independent aligned byte model" {
211     comptime {
212         @stardustClaim(
213             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "gui_surface_clone_capacity"),
214             null,
215             null,
216             null,
217             null,
218             null,
219             null,
220         );
221     }
222 
223     const cases = [_]Limits{
224         .{ .nodes = 1, .semantic_bytes = 0 },
225         .{
226             .nodes = 29,
227             .runs = 41,
228             .styles = 12,
229             .images = 4,
230             .image_pixels = 16_384,
231             .semantic_bytes = 1_440,
232         },
233         .{
234             .nodes = 257,
235             .runs = 2_048,
236             .styles = 512,
237             .images = 16,
238             .image_pixels = 65_536,
239             .semantic_bytes = 65_536,
240         },
241     };
242     for (cases) |limits| {
243         try std.testing.expectEqual(try modelCapacity(limits), try Capacity.derive(limits));
244     }
245 }
246 
247 test "surface clone capacity rejects invalid and overflowing limits" {
248     try std.testing.expectError(error.InvalidNodeLimit, Capacity.derive(.{
249         .nodes = 0,
250         .semantic_bytes = 0,
251     }));
252     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
253         .nodes = std.math.maxInt(usize),
254         .runs = std.math.maxInt(usize),
255         .styles = std.math.maxInt(usize),
256         .images = std.math.maxInt(usize),
257         .image_pixels = std.math.maxInt(usize),
258         .semantic_bytes = std.math.maxInt(usize),
259     }));
260 }
261 
262 comptime {
263     std.debug.assert(std.math.isPowerOfTwo(storage_alignment));
264 }