Last active
November 18, 2023 19:18
-
-
Save tauoverpi/e97dd10d9dc7a95b40806256953ed346 to your computer and use it in GitHub Desktop.
ecs-mini.zig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
data: Data = undefined, | |
len: Length, | |
systems: Systems = .{}, | |
/// Run all systems for a single logical game update. | |
pub fn update(ecs: *EntityComponentSystem, dt: f64) void { | |
inline for (@typeInfo(Systems).Struct.fields) |field| { | |
@field(ecs.systems, field.name).run(&ecs.data, &ecs.len, dt); | |
} | |
} | |
pub fn run(ecs: *EntityComponentSystem, system: anytype, dt: f64) void { | |
system.run(&ecs.data, &ecs.len, dt); | |
} | |
pub const Entity = packed struct(u16) { | |
type: Entity.Type, | |
id: Int, | |
pub const Type = std.meta.FieldEnum(Data); | |
pub const Int = @Type(.{ .Int = .{ | |
.signedness = .unsigned, | |
.bits = 16 - @bitSizeOf(Entity.Type), | |
} }); | |
}; | |
pub const Length = typ: { | |
const info = @typeInfo(Data).Struct; | |
var fields: [info.fields.len]Type.StructField = undefined; | |
for (&fields, info.fields) |*field, desc| { | |
field.* = .{ | |
.name = desc.name, | |
.type = u16, | |
.alignment = 2, | |
.default_value = &@as(u16, 0), | |
.is_comptime = false, | |
}; | |
} | |
break :typ @Type(.{ .Struct = .{ | |
.layout = .Extern, | |
.fields = &fields, | |
.decls = &.{}, | |
.is_tuple = false, | |
} }); | |
}; | |
pub fn Component(comptime entries: u16, comptime T: type) type { | |
const info = @typeInfo(T).Struct; | |
var fields: [info.fields.len]Type.StructField = undefined; | |
for (&fields, info.fields) |*field, desc| { | |
field.* = .{ | |
.name = desc.name, | |
.type = [entries]desc.type, | |
.alignment = 0, | |
.default_value = null, | |
.is_comptime = false, | |
}; | |
} | |
return @Type(.{ .Struct = .{ | |
.layout = .Extern, | |
.fields = &fields, | |
.decls = &.{}, | |
.is_tuple = false, | |
} }); | |
} | |
pub const Guard = enum { skip, run }; | |
pub fn Runner( | |
comptime System: type, | |
comptime kind: enum { pure, render }, | |
comptime components: []const @Type(.EnumLiteral), | |
) type { | |
const Fn = @TypeOf(@field(System, "update")); | |
const params = @typeInfo(Fn).Fn.params; | |
const start = switch (kind) { | |
.pure => 2, | |
.render => 3, | |
}; | |
const Offset = struct { | |
len: u16, | |
data: typ: { | |
var fields: [components.len]Type.StructField = undefined; | |
for (&fields, components) |*field, spec| { | |
const name = @tagName(spec); | |
field.* = .{ | |
.name = name, | |
.type = u16, | |
.alignment = 2, | |
.default_value = null, | |
.is_comptime = false, | |
}; | |
} | |
break :typ @Type(.{ .Struct = .{ | |
.layout = .Extern, | |
.fields = &fields, | |
.decls = &.{}, | |
.is_tuple = false, | |
} }); | |
}, | |
}; | |
const offsets = off: { | |
var offsets: []const Offset = &.{}; | |
loop: for (@typeInfo(Data).Struct.fields) |field| { | |
var offset: Offset = .{ | |
.len = @offsetOf(Length, field.name) >> 1, | |
.data = undefined, | |
}; | |
for (components, params[start..]) |spec, param| { | |
const name = @tagName(spec); | |
if (!@hasField(field.type, name)) continue :loop; | |
const FT = @typeInfo(@TypeOf(@field(@as(field.type, undefined), name))).Array.child; | |
const C = @typeInfo(param.type.?).Pointer.child; | |
if (C != FT) @compileError(@typeName(C) ++ " != " ++ @typeName(FT)); | |
@field(offset.data, name) = // | |
@offsetOf(field.type, name) + | |
@offsetOf(Data, field.name); | |
} | |
offsets = offsets ++ &[_]Offset{offset}; | |
} | |
break :off offsets; | |
}; | |
return struct { | |
pub fn run(sys: *System, data: *Data, len: *Length, dt: f64) void { | |
const data_p: [*]u8 = @ptrCast(data); | |
const len_p: [*]u16 = @ptrCast(len); | |
if (@hasDecl(System, "guard")) switch (sys.guard(dt)) { | |
.run => {}, | |
.skip => return, | |
}; | |
if (@hasDecl(System, "begin")) { | |
sys.begin(); | |
} | |
for (offsets) |offset| { | |
var args: std.meta.ArgsTuple(Fn) = undefined; | |
args[0] = sys; | |
args[1] = len_p[offset.len]; | |
switch (kind) { | |
.pure => {}, | |
.render => args[2] = dt, | |
} | |
comptime var index = start; | |
inline for (params[start..], components) |param, spec| if (param.type.? != void) { | |
const name = @tagName(spec); | |
const off = @field(offset.data, name); | |
args[index] = @ptrCast(@alignCast(data_p + off)); | |
index += 1; | |
}; | |
@call(.auto, System.update, args); | |
} | |
if (@hasDecl(System, "end")) { | |
sys.end(); | |
} | |
} | |
}; | |
} | |
const std = @import("std"); | |
const Type = std.builtin.Type; | |
const EntityComponentSystem = @This(); | |
pub const Data = @import("Data.zig"); | |
pub const Systems = @import("Systems.zig"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment