Backlog/engine/core/src/engineObject.zig

413 lines
13 KiB
Zig

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,
JobsAbortShutdown,
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;
}
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 make(ptr: anytype, comptime funcName: []const u8) @This() {
const TargetType = @typeInfo(@TypeOf(ptr)).pointer.child;
const func = @field(TargetType, funcName);
return .{
.func = func,
.functionName = funcName,
.target = ptr,
.vtable = &TargetType.NeonObjectTable,
};
}
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,
// new init_function passes in an already created object
init_func: *const fn (*anyopaque, std.mem.Allocator, bool) EngineDataEventError!void,
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 {
pub fn func(p: *anyopaque, allocator: std.mem.Allocator, first: bool) EngineDataEventError!void {
const newObject: *TargetType = @ptrCast(@alignCast(p)); // funcFind(allocator) catch return error.BadInit;
newObject.init(allocator, first) catch return error.BadInit;
// return @as(*anyopaque, @ptrCast(newObject));
}
};
self.init_func = wrappedInit.func;
}
if (@hasDecl(TargetType, "create")) {
const wrappedInit = struct {
pub fn func(p: *anyopaque, allocator: std.mem.Allocator, first: bool) EngineDataEventError!void {
const newObject: *TargetType = @ptrCast(@alignCast(p)); // funcFind(allocator) catch return error.BadInit;
newObject.create(allocator, first) 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);