Skip to content
This repository was archived by the owner on Mar 28, 2026. It is now read-only.

Commit 53be26e

Browse files
committed
move Plugin out to separate file
1 parent 6e417b1 commit 53be26e

2 files changed

Lines changed: 221 additions & 217 deletions

File tree

src/core/Plugin.zig

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
const std = @import("std");
2+
const zigplug = @import("root.zig");
3+
4+
const Plugin = @This();
5+
6+
pub const Log = struct {
7+
pub const LogFn = *const fn (
8+
context: *anyopaque,
9+
level: std.log.Level,
10+
text: [:0]const u8,
11+
) void;
12+
13+
context: *anyopaque,
14+
logFn: LogFn = default,
15+
allocator: std.mem.Allocator,
16+
17+
pub fn default(_: *anyopaque, level: std.log.Level, text: [:0]const u8) void {
18+
switch (level) {
19+
.err => std.log.err("{s}", .{text}),
20+
.warn => std.log.warn("{s}", .{text}),
21+
.info => std.log.info("{s}", .{text}),
22+
.debug => std.log.debug("{s}", .{text}),
23+
}
24+
}
25+
26+
const stack_size = 512;
27+
28+
pub inline fn log(self: *const Log, comptime level: std.log.Level, comptime format: []const u8, args: anytype) void {
29+
if (comptime std.log.defaultLogEnabled(level)) {
30+
var stack_allocator = std.heap.stackFallback(stack_size, self.allocator);
31+
const allocator = stack_allocator.get();
32+
const text = std.fmt.allocPrintSentinel(allocator, format, args, 0) catch return;
33+
defer allocator.free(text);
34+
self.logFn(self.context, level, text);
35+
}
36+
}
37+
38+
pub inline fn debug(self: *const Log, comptime format: []const u8, args: anytype) void {
39+
self.log(.debug, format, args);
40+
}
41+
42+
pub inline fn err(self: *const Log, comptime format: []const u8, args: anytype) void {
43+
self.log(.err, format, args);
44+
}
45+
46+
pub inline fn info(self: *const Log, comptime format: []const u8, args: anytype) void {
47+
self.log(.info, format, args);
48+
}
49+
50+
pub inline fn warn(self: *const Log, comptime format: []const u8, args: anytype) void {
51+
self.log(.warn, format, args);
52+
}
53+
};
54+
55+
context: *anyopaque,
56+
vtable: struct {
57+
deinit: *const fn (*anyopaque) void,
58+
process: *const fn (*anyopaque, zigplug.ProcessBlock, ?*const anyopaque) anyerror!void,
59+
},
60+
61+
allocator: std.mem.Allocator,
62+
log: *Log,
63+
parameters: ?zigplug.parameters.State,
64+
sample_rate_hz: u32 = 0,
65+
66+
pub fn init(comptime T: type) !Plugin {
67+
if (!@hasDecl(T, "meta") or @TypeOf(T.meta) != zigplug.Meta)
68+
@compileError(
69+
\\Plugin is missing a metadata object.
70+
\\
71+
\\Add one to your root plugin struct:
72+
\\`pub const meta = @import("zigplug").Meta{...};`
73+
);
74+
75+
validateFunction(T, "init", &.{}, anyerror!T);
76+
validateFunction(T, "deinit", &.{*T}, void);
77+
validateFunction(T, "allocator", &.{*T}, std.mem.Allocator);
78+
validateFunction(
79+
T,
80+
"process",
81+
if (@hasDecl(T, "Parameters"))
82+
&.{ *T, zigplug.ProcessBlock, *const T.Parameters }
83+
else
84+
&.{ *T, zigplug.ProcessBlock },
85+
anyerror!void,
86+
);
87+
88+
const context = try std.heap.page_allocator.create(T);
89+
context.* = try T.init();
90+
91+
const allocator = context.allocator();
92+
93+
const log = try allocator.create(Log);
94+
log.* = .{
95+
// SAFETY: only accessed when logFn is different than defaultLog
96+
.context = undefined,
97+
.allocator = allocator,
98+
};
99+
100+
return .{
101+
.context = context,
102+
.vtable = .{
103+
.deinit = @ptrCast(&T.deinit),
104+
.process = @ptrCast(&T.process),
105+
},
106+
.allocator = allocator,
107+
.log = log,
108+
.parameters = if (@hasDecl(T, "Parameters")) blk: {
109+
const Parameters = T.Parameters;
110+
switch (@typeInfo(Parameters)) {
111+
.@"struct" => |info| {
112+
inline for (info.fields) |field| {
113+
if (field.type != zigplug.Parameter)
114+
@compileError("`Parameters` struct field '" ++ field.name ++ "' is not of type `zigplug.Parameter`");
115+
if (field.defaultValue() == null)
116+
@compileError("`Parameters` struct field '" ++ field.name ++ "' has no default value");
117+
}
118+
},
119+
else => @compileError("`Parameters` is not a struct"),
120+
}
121+
122+
const parameters_context = try allocator.create(Parameters);
123+
parameters_context.* = .{};
124+
const fields = @typeInfo(Parameters).@"struct".fields;
125+
126+
var parameters_slice = try allocator.alloc(*zigplug.Parameter, fields.len);
127+
128+
inline for (fields, 0..) |field, i| {
129+
const param = &@field(parameters_context, field.name);
130+
switch (param.*) {
131+
inline else => |*p| p.options.id = p.options.id orelse field.name,
132+
}
133+
parameters_slice[i] = param;
134+
}
135+
136+
break :blk .{
137+
.context = parameters_context,
138+
.log = log,
139+
.slice = parameters_slice,
140+
.allocator = allocator,
141+
};
142+
} else null,
143+
};
144+
}
145+
146+
pub inline fn deinit(self: *Plugin, comptime P: type) void {
147+
self.allocator.destroy(self.log);
148+
149+
if (@hasDecl(P, "Parameters")) {
150+
const params = self.parameters.?;
151+
self.allocator.free(params.slice);
152+
const ptr: *P.Parameters = @ptrCast(@alignCast(params.context));
153+
self.allocator.destroy(ptr);
154+
}
155+
156+
self.vtable.deinit(self.context);
157+
158+
const plugin: *P = @ptrCast(@alignCast(self.context));
159+
std.heap.page_allocator.destroy(plugin);
160+
}
161+
162+
pub inline fn process(self: *Plugin, block: zigplug.ProcessBlock, params: ?*const anyopaque) !void {
163+
try self.vtable.process(self.context, block, params);
164+
}
165+
166+
pub fn setLog(self: *Plugin, context: *anyopaque, logFn: Log.LogFn) void {
167+
self.log.context = context;
168+
self.log.logFn = logFn;
169+
}
170+
171+
// error unions make `==` type comparison wonky so we have to compare arg and return types manually
172+
fn validateFunction(comptime Container: type, comptime name: []const u8, comptime args: []const type, Return: type) void {
173+
const ExpectedType = @Type(.{ .@"fn" = .{
174+
.params = comptime blk: {
175+
var params: [args.len]std.builtin.Type.Fn.Param = undefined;
176+
for (args, 0..) |Arg, i|
177+
params[i] = .{
178+
.type = Arg,
179+
.is_generic = false,
180+
.is_noalias = false,
181+
};
182+
break :blk &params;
183+
},
184+
.return_type = Return,
185+
.calling_convention = .auto,
186+
.is_var_args = false,
187+
.is_generic = false,
188+
} });
189+
190+
if (!@hasDecl(Container, name))
191+
@compileError("Plugin is missing method '" ++ name ++ "' of type '" ++ @typeName(ExpectedType) ++ "'");
192+
193+
const Fn = @TypeOf(@field(Container, name));
194+
const info = @typeInfo(Fn).@"fn";
195+
const msg = "Wrong signature for method '" ++ name ++ "': expected '" ++ @typeName(ExpectedType) ++ "', found '" ++ @typeName(Fn) ++ "'";
196+
197+
const Actual = info.return_type orelse void;
198+
199+
switch (@typeInfo(Actual)) {
200+
.error_union => |actual_error_union| {
201+
switch (@typeInfo(Return)) {
202+
.error_union => |expected_error_union| {
203+
if (actual_error_union.payload != expected_error_union.payload)
204+
@compileError(msg);
205+
},
206+
else => @compileError(msg),
207+
}
208+
},
209+
else => if (Actual != Return) @compileError(msg),
210+
}
211+
212+
const params = info.params;
213+
if (params.len != args.len)
214+
@compileError(msg);
215+
216+
inline for (params, args) |param, Arg| {
217+
if (param.type != Arg)
218+
@compileError(msg);
219+
}
220+
}

0 commit comments

Comments
 (0)