const std = @import("std"); pub const states = @import("./states.zig"); pub const system = @import("./system.zig"); pub const World = @import("./World.zig"); pub fn Read(comptime Value: type) type { return Shared(Value, .{ .thread_restriction = states.thread_restriction(Value), .read_only = true, }); } pub const ShareInfo = struct { thread_restriction: states.ThreadRestriction, read_only: bool, }; pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { const value_info = @typeInfo(Value); const Qualified = switch (value_info) { .Optional => @Type(.{ .Optional = .{ .child = .{ .Pointer = .{ .is_allowzero = false, .sentinel = null, .address_space = .generic, .is_volatile = false, .alignment = @alignOf(Value), .size = .One, .child = Value, .is_const = info.read_only, }, }, }, }), else => @Type(.{ .Pointer = .{ .is_allowzero = false, .sentinel = null, .address_space = .generic, .is_volatile = false, .alignment = @alignOf(Value), .size = .One, .child = Value, .is_const = info.read_only, }, }), }; return struct { res: Qualified, const Self = @This(); pub const State = struct { res: Qualified, }; pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State { const thread_restriction_name = switch (info.thread_restriction) { .main => "main thread-restricted ", .none => "" }; const res = switch (info.read_only) { true => (try context.register_readable_resource_access(Value)), false => (try context.register_writable_resource_access(Value)), }; return .{ .res = switch (value_info) { .Optional => res, else => res orelse { @panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{ thread_restriction_name, if (info.read_only) "read-only" else "read-write", @typeName(Value), })); }, }, }; } pub fn init(state: *State) Self { return .{ .res = state.res, }; } }; } pub fn Write(comptime Value: type) type { return Shared(Value, .{ .thread_restriction = states.thread_restriction(Value), .read_only = false, }); } fn parameter_type(comptime Value: type) *const system.Info.Parameter { const has_state = @hasDecl(Value, "State"); if (@sizeOf(Value) == 0) { @compileError("System parameters must have a non-zero size"); } const parameters = struct { fn bind(allocator: std.mem.Allocator, context: system.BindContext) std.mem.Allocator.Error!?*anyopaque { if (has_state) { const value_name = @typeName(Value); if (!@hasDecl(Value, "bind")) { @compileError( "a `bind` declaration on " ++ value_name ++ " is requied for parameter types with a `State` declaration"); } const bind_type = @typeInfo(@TypeOf(Value.bind)); if (bind_type != .Fn) { @compileError("`bind` declaration on " ++ value_name ++ " must be a fn"); } if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != system.BindContext) { @compileError( "`bind` fn on " ++ value_name ++ " must accept " ++ @typeName(system.BindContext) ++ " as it's one and only argument"); } const state = try allocator.create(Value.State); state.* = switch (bind_type.Fn.return_type.?) { Value.State => Value.bind(context), std.mem.Allocator.Error!Value.State => try Value.bind(context), else => @compileError( "`bind` fn on " ++ @typeName(Value) ++ " must return " ++ @typeName(Value.State) ++ " or " ++ @typeName(std.mem.Allocator.Error!Value.State)), }; return @ptrCast(state); } else { return null; } } fn init(argument: *anyopaque, state: ?*anyopaque) void { const value_name = @typeName(Value); if (!@hasDecl(Value, "init")) { @compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types"); } const init_type = @typeInfo(@TypeOf(Value.init)); if (init_type != .Fn) { @compileError("`init` declaration on " ++ value_name ++ " must be a fn"); } if (init_type.Fn.return_type.? != Value) { @compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name); } const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument))); if (has_state) { if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.State) { @compileError("`init` fn on stateful " ++ value_name ++ " must accept a " ++ @typeName(*Value.State)); } concrete_argument.* = Value.init(@ptrCast(@alignCast(state.?))); } else { if (init_type.Fn.params.len != 0) { @compileError("`init` fn on statelss " ++ value_name ++ " cannot use parameters"); } concrete_argument.* = Value.init(); } } fn unbind(allocator: std.mem.Allocator, state: ?*anyopaque) void { if (@hasDecl(Value, "unbind")) { if (has_state) { const typed_state = @as(*Value.State, @ptrCast(@alignCast(state.?))); Value.unbind(typed_state); allocator.destroy(typed_state); } else { Value.unbind(); } } } }; return comptime &.{ .thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none, .init = parameters.init, .bind = parameters.bind, .unbind = parameters.unbind, }; } pub fn system_fn(comptime call: anytype) *const system.Info { const Call = @TypeOf(call); const system_info = comptime generate: { switch (@typeInfo(Call)) { .Fn => |call_fn| { if (call_fn.params.len > system.max_parameters) { @compileError("number of parameters to `call` cannot be more than 16"); } const systems = struct { fn run(parameters: []const *const system.Info.Parameter, data: *const [system.max_parameters]?*anyopaque) anyerror!void { var call_args = @as(std.meta.ArgsTuple(Call), undefined); inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| { parameter.init(call_arg, state); } switch (@typeInfo(call_fn.return_type.?)) { .Void => @call(.auto, call, call_args), .ErrorUnion => try @call(.auto, call, call_args), else => @compileError("number of parameters to `call` must return void or !void"), } } }; var parameters = @as([system.max_parameters]*const system.Info.Parameter, undefined); var thread_restriction = states.ThreadRestriction.none; for (0 .. call_fn.params.len) |index| { const CallParam = call_fn.params[index].type.?; const parameter = parameter_type(CallParam); if (parameter.thread_restriction != .none) { if (thread_restriction != .none and thread_restriction != parameter.thread_restriction) { @compileError("a system may not have conflicting thread restrictions"); } thread_restriction = parameter.thread_restriction; } parameters[index] = parameter; } break: generate &.{ .parameters = parameters, .parameter_count = call_fn.params.len, .execute = systems.run, .thread_restriction = thread_restriction, }; }, else => @compileError("parameter `call` must be a function"), } }; return system_info; }