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, }; const FuncInfo = struct { name: []const u8, ptr: *const anyopaque, }; pub const FieldInfo = struct { name: []const u8, size: u32, offset: u32, alignment: u32, }; const core = @import("core.zig"); pub fn updateEngineVTable(comptime T: type) void { const ref = core.getEngineObjectRef(T).?; const vtable: *EngineObjectVTable = @constCast(ref.vtable); const version = vtable.version; if (ref.vtable.fieldList) |fieldList| { core.PatchStruct(T, @ptrCast(@alignCast(ref.ptr)), fieldList); } vtable.* = T.NeonObjectTable; vtable.version = version + 1; if (@hasDecl(T, "objectReload")) { core.get(T).objectReload(); } } 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.debug.print( // "patching field {s} {d} bytes {d} -> {d} {s}\n", // .{ // field.name, // src.len, // of.offset, // @offsetOf(T, field.name), // if (of.offset == @offsetOf(T, field.name)) "" else "change!", // }, // ); std.mem.copyForwards(u8, dest, src); } else { std.debug.print("new field added {s}", .{field.name}); } } p.* = new; } pub fn EngineObjectDelegate(comptime T: type) type { return struct { func: ?T = null, target: *anyopaque, functionName: []const u8, version: i32 = -1, vtable: *EngineObjectVTable, pub fn call(self: *@This(), args: anytype) void { //if (!core.BuildOption("static_build")) { todo; disable this vtable check in non-static builds if (self.vtable.version != self.version) { self.version = self.vtable.version; if (self.vtable.findFunc(self.functionName)) |p| { self.func = @ptrCast(@alignCast(p)); } else { core.engine_log("unable to find function {s} in {s}", .{ self.functionName, self.vtable.typeName }); self.func = null; } } //} if (self.func) |func| { func(self.target, args); } } }; } 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, funcList: ?[]const FuncInfo = null, version: i32 = 0, pub fn findFunc(self: *const @This(), name: []const u8) ?*const anyopaque { if (self.funcList) |flist| { for (flist) |fi| { // core.engine_log("checking func {s} vs {s}.", .{ fi.name, name }); if (std.mem.eql(u8, fi.name, name)) { return fi.ptr; } } } return null; } pub fn addFunctionsList(self: *@This(), comptime TargetType: type) void { const ti = @typeInfo(TargetType); const funcList = blk: { comptime var f: []const FuncInfo = &.{}; inline for (ti.@"struct".decls) |decl| { if (std.mem.eql(u8, decl.name, "NeonObjectTable")) { continue; } const t = @typeInfo(@TypeOf(@field(TargetType, decl.name))); switch (t) { .@"fn" => { // @compileLog(decl.name); f = f ++ .{FuncInfo{ .name = decl.name, .ptr = @ptrCast(@alignCast(&@field(TargetType, decl.name))), }}; }, else => {}, } } break :blk f; }; self.funcList = funcList; } 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); self.addFunctionsList(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);