const std = @import("std"); const logging = @import("logging.zig"); const p2 = @import("p2"); const engine_logs = logging.engine_logs; const ObjectHandle = p2.ObjectHandle; const Name = p2.Name; const MakeName = p2.MakeName; const ArrayList = std.ArrayList; const AutoHashMap = std.AutoHashMap; const ArrayListUnmanaged = std.ArrayListUnmanaged; const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged; pub fn InterfaceRef(comptime Vtable: type) type { return struct { ptr: *anyopaque, vtable: *const Vtable, }; } pub fn InterfaceRef2(comptime Vtable: type) type { return struct { ptr: *anyopaque, vtable: *Vtable, }; } pub fn MakeTypeName(comptime TargetType: type) Name { const hashedName = comptime std.fmt.comptimePrint("{s}_{d}", .{ @typeName(TargetType), @sizeOf(TargetType) }); return MakeName(hashedName); } // todo.. engineObject is not the right word for this pub const EngineDataEventError = error{ UnknownStatePanic, BadInit, UnknownError, OutOfMemory, }; pub const FieldInfo = struct { name: []const u8, size: u32, offset: u32, alignment: u32, }; pub fn PatchStruct(comptime T: type, p: *T, old: []const FieldInfo) void { const t = @typeInfo(T).@"struct"; var new: T = .{}; inline for (t.fields) |field| { var oldField: ?FieldInfo = null; for (old) |oldFieldSearch| { if (std.mem.eql(u8, field.name, oldFieldSearch.name)) { oldField = oldFieldSearch; break; } } if (oldField) |of| { var src: []const u8 = undefined; src.ptr = @ptrCast(p); src.ptr += of.offset; src.len = @sizeOf(@TypeOf(@field(p, field.name))); var dest: []u8 = undefined; dest.ptr = @ptrCast(&@field(new, field.name)); dest.len = @sizeOf(@TypeOf(@field(new, field.name))); std.mem.copyForwards(u8, dest, src); } } p.* = new; } pub const EngineObjectVTable = struct { typeName: []const u8, typeSize: usize, typeAlign: usize, singletonName: ?[]const u8 = null, init_func: *const fn (std.mem.Allocator) EngineDataEventError!*anyopaque, tick_func: ?*const fn (*anyopaque, f64) void = null, engineDraw_func: ?*const fn (*anyopaque, f64) void = null, preTick_func: ?*const fn (*anyopaque, f64) EngineDataEventError!void = null, deinit_func: ?*const fn (*anyopaque) void = null, postInit_func: ?*const fn (*anyopaque) EngineDataEventError!void = null, processEvents: ?*const fn (*anyopaque, u64) EngineDataEventError!void = null, exitSignal_func: ?*const fn (*anyopaque) EngineDataEventError!void = null, readyToExit_func: ?*const fn (*anyopaque) bool = null, prepare_func: ?*const fn (*anyopaque) EngineDataEventError!void = null, fieldListHash: ?usize = null, fieldList: ?[]const FieldInfo = null, slackSize: ?usize = null, fn fieldInfoCompare(_: void, a: FieldInfo, b: FieldInfo) bool { return a.offset < b.offset; } pub fn addFieldList(self: *@This(), comptime TargetType: type) void { if (!@hasDecl(TargetType, "Slack")) { return; } self.slackSize = @sizeOf(TargetType.Slack); const typeinfo = @typeInfo(TargetType).@"struct"; const fieldList = blk: { comptime var f: []const FieldInfo = &.{}; inline for (typeinfo.fields) |field| { f = f ++ .{FieldInfo{ .name = field.name, .offset = @intCast(@offsetOf(TargetType, field.name)), .size = @intCast(@sizeOf(field.type)), .alignment = @alignOf(field.type), }}; } break :blk f; }; self.fieldList = fieldList; } pub fn from(comptime TargetType: type, comptime engineObjectName: ?[]const u8) EngineObjectVTable { var self = EngineObjectVTable{ .typeName = @typeName(TargetType), .typeSize = @sizeOf(TargetType), .typeAlign = @alignOf(TargetType), .init_func = undefined, }; self.addFieldList(TargetType); if (engineObjectName) |eon| { self.singletonName = eon; } if (@hasDecl(TargetType, "init")) { const wrappedInit = struct { const funcFind: @TypeOf(@field(TargetType, "init")) = @field(TargetType, "init"); pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque { const newObject = funcFind(allocator) catch return error.BadInit; return @as(*anyopaque, @ptrCast(newObject)); } }; self.init_func = wrappedInit.func; } if (@hasDecl(TargetType, "create")) { const wrappedInit = struct { const funcFind: @TypeOf(@field(TargetType, "create")) = @field(TargetType, "create"); pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque { const newObject = funcFind(allocator) catch return error.BadInit; return @as(*anyopaque, @ptrCast(newObject)); } }; self.init_func = wrappedInit.func; } // function used for objects created before engine loop starts; // this is a defferred startup // // will run normally during create if in systems thread // // or else will run if (@hasDecl(TargetType, "prepare")) { const wrap = struct { pub fn func(pointer: *anyopaque) EngineDataEventError!void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); ptr.prepare() catch return error.BadInit; } }; self.prepare_func = wrap.func; } if (@hasDecl(TargetType, "postInit")) { const wrappedPostInit = struct { pub fn func(pointer: *anyopaque) EngineDataEventError!void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); try ptr.postInit(); } }; self.postInit_func = wrappedPostInit.func; } if (@hasDecl(TargetType, "preTick")) { const Wrapped = struct { pub fn func(pointer: *anyopaque, deltaTime: f64) EngineDataEventError!void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); ptr.preTick(deltaTime) catch return error.UnknownStatePanic; } }; self.preTick_func = Wrapped.func; } if (@hasDecl(TargetType, "engineDraw")) { const Wrapped = struct { pub fn func(pointer: *anyopaque, deltaTime: f64) void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); ptr.engineDraw(deltaTime); } }; self.engineDraw_func = Wrapped.func; } if (@hasDecl(TargetType, "tick")) { const Wrapped = struct { pub fn func(pointer: *anyopaque, deltaTime: f64) void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); ptr.tick(deltaTime); } }; self.tick_func = Wrapped.func; } if (@hasDecl(TargetType, "processEvents")) { const wrappedProcessEvents = struct { pub fn func(pointer: *anyopaque, frameNumber: u64) EngineDataEventError!void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); try ptr.processEvents(frameNumber); } }; self.processEvents = wrappedProcessEvents.func; } if (@hasDecl(TargetType, "onExitSignal")) { if (!@hasDecl(TargetType, "readyToExit")) { @compileError("engine object implemented onExitSignal but did not implement fn readyToExit() bool"); } const wrappedProcessEvents = struct { pub fn onExitSignal(pointer: *anyopaque) EngineDataEventError!void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); try ptr.onExitSignal(); } pub fn readyToExit(pointer: *anyopaque) bool { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); return ptr.readyToExit(); } }; self.exitSignal_func = wrappedProcessEvents.onExitSignal; self.readyToExit_func = wrappedProcessEvents.readyToExit; } if (@hasDecl(TargetType, "deinit")) { const wrappedDeinit = struct { pub fn func(pointer: *anyopaque) void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); ptr.deinit(); } }; self.deinit_func = wrappedDeinit.func; } if (@hasDecl(TargetType, "destroy")) { const wrappedDeinit = struct { pub fn func(pointer: *anyopaque) void { var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer))); ptr.destroy(); } }; self.deinit_func = wrappedDeinit.func; } return self; } }; pub const EngineObjectRef = InterfaceRef2(EngineObjectVTable);