// lwdt can't have meta tables so I'm going to create component references // // the way this works is // - entities are created as POD types // - entities can get components added to them via entity:addComponent // - this returns a ComponentReference // - you can also get components from an entity via enity:get() // - this also returns a ComponentReference // // - ComponentReferences allow you to modify data on a component or call functions on them // // // How the registration works // // - define component // - ecs.zig defineComponent // - ComponentRef.zig - ReferenceType // - addComponentRegistration - script.zig // - ComponentRef - ReferenceType.registerType // this represents the lua side of the object pub fn ComponentReferenceType(comptime T: type) type { return struct { ptr: *T = undefined, // used for resolving deltas. handle: core.ObjectHandle = undefined, stateCount: u32 = 0, containerRef: ecs.EcsContainerRef = undefined, pub const MetatableName = T.ComponentName; // argc = 1, // 1. a componentRegistration userdata // can only be called from entity.luaAddComponent pub fn luaNew(state: lua.LuaState, handle: core.ObjectHandle, ptr: ?*anyopaque) void { const ud = state.newZigUserdata(@This()) catch return; const containerRef = ecs.getTypeContainer(T); ud.* = .{ .handle = handle, .containerRef = containerRef, .stateCount = 0, }; // std.debug.print("luaNew ComponentReferenceType: {x}\n", .{@intFromPtr(ud)}); // std.debug.print("luaNew ContainerRef: {x}\n", .{@intFromPtr(containerRef.ptr)}); // std.debug.print("luaNew " ++ @typeName(T) ++ " ud.ptr = {x}\n", .{@intFromPtr(ptr)}); if (ptr) |p| { ud.ptr = @ptrCast(@alignCast(p)); ud.stateCount = containerRef.vtable.getStateCount(containerRef.ptr); } if (@hasDecl(@TypeOf(T.BaseContainer.*), "IsMultiset")) { // ... there are several things that need to be reworked here... ud.ptr = @ptrCast(@alignCast(@as(*anyopaque, @ptrCast(&ud.handle)))); // std.debug.print("luaNew " ++ @typeName(T) ++ " v = {any}\n", .{ud.ptr.getPosition()}); } } pub fn resolve(self: *@This()) void { const ref = self.containerRef; // std.debug.print("self: {x}\n", .{@intFromPtr(self)}); // std.debug.print("ptr: {x}\n", .{@intFromPtr(ref.ptr)}); if (@hasDecl(@TypeOf(T.BaseContainer.*), "IsMultiset")) { // } if (ref.vtable.getStateCount(ref.ptr) != self.stateCount) { self.ptr = @ptrCast(@alignCast(ref.vtable.get(ref.ptr, self.handle))); } } pub fn get(self: *@This()) *T { self.resolve(); return self.ptr; } pub fn luaToString(state: lua.LuaState) i32 { // oh god... this isn't good // I think i've been treating this component ref as the user type // huge failure of type resolution if (state.toUserdata(@This(), 1)) |self| { self.resolve(); Buffer.clearRetainingCapacity(); var writer = Buffer.writer(); writer.print("{s}{{", .{MetatableName}) catch return 0; inline for (std.meta.fields(T), 0..) |field, i| { if (i == 0) { writer.print(" {s} = ", .{field.name}) catch return 0; } else { writer.print(", {s} = ", .{field.name}) catch return 0; } switch (field.type) { f32, i32, u32, f64, i64, u64 => { writer.print("{d}", .{@field(self.ptr, field.name)}) catch return 0; }, []const u8 => { writer.print("\"{s}\"", .{@field(self.ptr, field.name)}) catch return 0; }, else => { writer.print("", .{@typeName(field.type)}) catch return 0; }, } } writer.print(" }}" ++ "\x00", .{}) catch return 0; state.pop(1); state.pushString(Buffer.items) catch return 0; return 1; } return 0; } pub fn luaIndex(state: lua.LuaState) i32 { if (state.toUserdata(@This(), 1)) |self| { if (state.isString(2)) { _ = self; const argument = state.toString(2); // core.engine_log(@typeName(@This()) ++ " got indexed. 0x{x}", .{self.handle.index}); // check metatable if (state.getMetafield(1, @ptrCast(argument))) { return 1; } } } return 0; } fn makeTypeTable() lua.LibSpec { const methods = blk: { comptime var m: lua.LibSpec = &.{}; m = m ++ .{lua.luaL_Reg{ .name = "__index", .func = lua.CWrap(luaIndex) }}; m = m ++ .{lua.luaL_Reg{ .name = "__tostring", .func = lua.CWrap(luaToString) }}; inline for (T.ScriptExports) |name| { m = m ++ .{lua.luaL_Reg{ .name = @as([*c]const u8, @ptrCast(name)), .func = ComponentFuncWrapper(@field(T, name), T) }}; } break :blk m ++ .{lua.luaL_Reg{ .name = null, .func = null }}; }; return methods; } pub fn registerType(state: *lua.LuaState) !void { core.engine_log("creating lua metatable {s}", .{MetatableName}); const methods = comptime makeTypeTable(); try state.newMetatable(@ptrCast(MetatableName)); try state.setFuncs(methods, 0); } }; } pub fn ComponentFuncWrapper(comptime baseFunc: anytype, comptime baseType: type) lua.LuaCFunc { return lua.CWrap(FuncWrapper(baseFunc, baseType).wrapper); } pub fn FuncWrapper(comptime baseFunc: anytype, comptime baseType: type) type { return struct { pub fn wrapper(state: lua.LuaState) i32 { const Args = std.meta.ArgsTuple(@TypeOf(baseFunc)); var args: Args = undefined; inline for (std.meta.fields(Args), 0..) |field, index| { // std.debug.print("typename = {s}\n", .{@typeName(field.type)}); switch (field.type) { f32 => { args[index] = @as(f32, @floatCast(state.toNumber(index + 1))); }, f64 => { args[index] = state.toNumber(index + 1); }, i32 => { args[index] = @intFromFloat(state.toNumber(index + 1)); }, []const u8 => { args[index] = state.toString(index + 1); }, // I'll be honest how the hell does this work? // // the pointer being passed in here isn't the actual resulting type... // it's the reference type *baseType => { const ref = state.toUserdata(ComponentReferenceType(baseType), index + 1).?; ref.resolve(); args[index] = ref.ptr; }, baseType => { const ref = state.toUserdata(ComponentReferenceType(baseType), index + 1).?; ref.resolve(); args[index] = ref.ptr.*; }, else => { // if(isComponentType(field.type)) { // const ref = state.toUserdata(field.type, index + 1).?; // ref.resolve(); // args[index] = ref.ptr.*; // continue; // } // lua.debugPrints(true); args[index] = (state.toUserdata(field.type, index + 1) orelse { std.debug.print("argument error in index: {d}\n", .{index}); state.emitError("something's weird with this argument\n"); @panic("lmao"); }).*; lua.debugPrints(false); }, } } state.pop(@intCast(args.len)); //const rv = @call(.always_inline, baseFunc, args); const rv = @call(.auto, baseFunc, args); switch (@TypeOf(rv)) { i32, u32, i64, u64 => { state.pushNumber(@floatFromInt(rv)); }, f32, f64 => { state.pushNumber(@floatCast(rv)); }, void => { return 0; }, else => { const ud = state.newZigUserdata(@TypeOf(rv)) catch @panic("not implemented"); ud.* = rv; }, } return 1; } }; } var Buffer: std.ArrayList(u8) = undefined; pub fn setupFormatBuffer(allocator: std.mem.Allocator) !void { Buffer = std.ArrayList(u8).init(allocator); } pub fn shutdownFormatBuffer() void { Buffer.deinit(); } const std = @import("std"); const core = @import("../core.zig"); const ecs = @import("../ecs.zig"); const lua = @import("lua"); const pod = lua.pod; const scene = @import("../scene.zig");