lib/choir/src/product/entity.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const revision = @import("root.zig").revision;
3 const bytecode = @import("../bytecode/root.zig");
4
5 /// A retained view owns only immutable bytes and bytecode lookup metadata.
6 pub const Image = opaque {
7 pub fn create(
8 allocator: std.mem.Allocator,
9 published: *const revision.Revision,
10 limits: bytecode.image.Limits,
11 expected_version: u32,
12 ) !*Image {
13 const retained = try published.retain();
14 errdefer retained.release();
15 const state = try allocator.create(ImageData);
16 errdefer allocator.destroy(state);
17 var reader = try revision.record.Reader.init(published.view().exact.image);
18 if (try reader.readInt(u32) != expected_version) return error.UnknownSchema;
19 const count = try reader.readCount();
20 if (count > limits.entities) return error.ImageLimit;
21 const roots = try allocator.alloc(*bytecode.image.Index, count);
22 errdefer allocator.free(roots);
23 var initialized: usize = 0;
24 errdefer for (roots[0..initialized]) |index| index.destroy();
25 for (roots) |*index| {
26 index.* = try bytecode.image.Index.create(allocator, try reader.readBlob(), limits);
27 initialized += 1;
28 }
29 state.* = .{
30 .allocator = allocator,
31 .revision = retained,
32 .roots = roots,
33 .stage = try reader.readBlob(),
34 };
35 if (!reader.atEnd()) return error.InvalidRecord;
36 return @ptrCast(state);
37 }
38
39 pub fn destroy(self: *Image) void {
40 const state = imageData(self);
41 for (state.roots) |index| index.destroy();
42 state.allocator.free(state.roots);
43 state.revision.release();
44 state.allocator.destroy(state);
45 }
46
47 pub fn root(self: *const Image, ordinal: u32) ?bytecode.image.View {
48 const state: *const ImageData = @ptrCast(@alignCast(self));
49 if (ordinal >= state.roots.len) return null;
50 return state.roots[ordinal].view();
51 }
52
53 pub fn stage(self: *const Image) []const u8 {
54 const state: *const ImageData = @ptrCast(@alignCast(self));
55 return state.stage;
56 }
57
58 pub fn operation(
59 self: *const Image,
60 ref: revision.store.Entity,
61 ) !Located(bytecode.image.Operation) {
62 return self.locate(ref, .operation, "operations", bytecode.image.Operation);
63 }
64
65 pub fn block(self: *const Image, ref: revision.store.Entity) !Located(bytecode.image.Block) {
66 return self.locate(ref, .block, "blocks", bytecode.image.Block);
67 }
68
69 pub fn value(self: *const Image, ref: revision.store.Entity) !Located(bytecode.image.Value) {
70 return self.locate(ref, .value, "values", bytecode.image.Value);
71 }
72
73 pub fn region(self: *const Image, ref: revision.store.Entity) !Located(bytecode.image.Region) {
74 return self.locate(ref, .region, "regions", bytecode.image.Region);
75 }
76
77 pub fn attribute(self: *const Image, ref: revision.store.Entity) !Located([]const u8) {
78 return self.locate(ref, .attribute, "attributes", []const u8);
79 }
80
81 fn locate(
82 self: *const Image,
83 ref: revision.store.Entity,
84 comptime namespace: revision.record.Namespace,
85 comptime field: []const u8,
86 comptime T: type,
87 ) !Located(T) {
88 const state: *const ImageData = @ptrCast(@alignCast(self));
89 if (!ref.revision.eql(state.revision)) return error.StaleEntity;
90 if (ref.namespace != namespace) return error.InvalidEntity;
91 var ordinal = ref.ordinal;
92 for (state.roots, 0..) |index, root_ordinal| {
93 const items = @field(index.view(), field);
94 if (ordinal < items.len) return .{
95 .root = @intCast(root_ordinal),
96 .value = items[ordinal],
97 };
98 ordinal -= @intCast(items.len);
99 }
100 return error.InvalidEntity;
101 }
102 };
103
104 pub fn Located(comptime T: type) type {
105 return struct { root: u32, value: T };
106 }
107
108 const ImageData = struct {
109 allocator: std.mem.Allocator,
110 revision: *const revision.Revision,
111 roots: []*bytecode.image.Index,
112 stage: []const u8,
113 };
114
115 fn imageData(image: *Image) *ImageData {
116 return @ptrCast(@alignCast(image));
117 }