Modules
A graphite module is a struct.
pub const MyModule = struct { _: u8 = 0, // registering a module with no fields causes a compilation error.
pub fn init(_: Allocator) !void { return MyModule{}; }
pub fn deinit(self: *MyModule) void {}};Some of its functions (hooks) will be inlined at compile time:
pub const MyModule = struct { ...
// if MyModule is registered, this functon will // be called inline when a message is sent. pub fn onChatMessage(h: hook.ChatMessageHook) !void { std.log.info("got message from {s}: {s}", .{ h.client.username.items, h.message, }); }};The dispatcher reflects on hook types at compile-time to inject required arguments. Any hook may take one of the following argument types, the ordering is interchangeable:
*@This(): current module*Context: game contextanytype: module registry*zcs.CmdBuf: a zcsCmdBuf
Since the final ModuleRegistry type is a tuple that depends on every registered
module types, individual modules can’t directly access it. Instead, the dispatcher
uses anytype. It’ll inject a pointer to the module registry for each parameter
defined with type anytype. See more.
A hook function may also take a specific structure that contains data regarding the triggered event. You may look up the sources to find more info on each struct.
Here are a few hook definitions:
pub const CounterModule = struct { counter: usize = 0,
...
// onJoin means the dispatcher will call this // hook when a player joins pub fn onJoin(self: *@This(), h: hook.JoinHook) !void { self.counter += 1; std.log.info("{s} joined, counter = {d}", .{ h.client.username.items, self.counter, }); }
// called when a player leaves pub fn onQuit(self: *@This(), client: *Client) !void { self.counter -= 1; sef.log.info("{s} left, counter = {d}", .{ h.client.username.items, self.counter, }); }};Registering a module
Section titled “Registering a module”Modules aren’t loaded dynamically at runtime like Bukkit plugins; registering a new module requires a recompilation of the Graphite codebase:
pub const Modules = .{ ... MyModule,};Module dependencies
Section titled “Module dependencies”Here are two module declarations:
// module_a.zig:pub const ModuleA = struct { counter: usize = 0,
...};
// module_b.zig:pub const ModuleB = struct { _: u8,
...};At runtime, ModuleB may look up ModuleA and read from/write to its state, and vice versa. Here’s an example:
pub const ModuleB = struct { ...
pub fn onJoin( reg: anytype, h: hook.JoinHook, ) !void { const module_a = reg.get(ModuleA); module_a.counter += 1; std.log.info("{s} joined, counter: {d}", .{ h.client.username.items, module_a.counter, }); }}If ModuleA isn’t registered when ModuleRegistry.get is evaluated, this won’t compile.
It would be common to express modules as functions that take comptime options and return a struct, such as:
pub const ModuleAOptions = struct { name: []const u8, some_flag: bool = true, some_number: i32 = 4, ...};
pub fn ModuleA(comptime opt: ModuleAOptions) type { return struct { counter: usize,
pub fn init(_: Allocator) !void { std.log.info("hello from " ++ opt.name ++ "!"); return @This(){ .counter = 0, }; } };}ModuleB can still depend on ModuleA:
pub const ModuleBOptions = struct { module_a: type,};
pub fn ModuleB(comptime opt: ModuleBOptions) type { return struct { _: u8,
...
pub fn onJoin(reg: anytype) !void { const module_a = reg.get(opt.module_a); module_a.counter += 1; std.log.info("counter: {d}", .{ module_a.counter }); } }}You’d need to register both modules for this to compile, here’s what this would look like:
const ModuleA = @import("module_a.zig").ModuleA(.{ name = "graphite", some_flag = true,});
const ModuleB = @import("module_b.zig").ModuleB(.{ module_a = ModuleA,});
pub const Modules = .{ ModuleA, ModuleB,};