// ok // // lets really cook a good API here pub const SpawnObjectResult = struct { ptr: *anyopaque, entity: core.Entity }; pub const ObjectCreateFn = *const fn (allocator: std.mem.Allocator, entity: core.Entity, parameters: SpawnParameters) ObjectError!*anyopaque; pub const MessageHandlerFunc = *const fn (*anyopaque, *const MessageInfo, *anyopaque) void; pub const GameObjectInterfaceVTable = struct { destroy: *const fn (*anyopaque, std.mem.Allocator) void, tick: ?*const fn (*anyopaque, f64) void, objectTypeName: core.Name = core.DefineName("UnknownType"), objectBaseName: core.Name = core.DefineName("UnknownObject"), create: ObjectCreateFn, messageHandlers: std.AutoHashMapUnmanaged(u32, MessageHandlerFunc) = .{}, pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { self.messageHandlers.deinit(allocator); } }; pub const ObjectError = error{ OutOfMemory, BadInit, UnknownState, UnknownObject, }; pub const MessageInfo = struct { tag: core.Name, sourceEntity: ?core.Entity = null, sourceSystem: ?core.Name = null, // maybe add like a filter type? or an invoker? }; pub const GameObjectRef = struct { table: *GameObjectInterfaceVTable, ptr: *anyopaque, }; pub const GameObject = struct { pub var BaseContainer: *core.SparseSet(GameObject) = undefined; pub const ComponentName = "GameObjectComponent"; pub const ScriptExports: []const []const u8 = &.{}; // set by GameObjectSystem when it calls Entity objectRef: GameObjectRef = undefined, destroyed: bool = false, pub fn deinitECS(self: *@This(), handle: core.ObjectHandle) void { _ = handle; if (!self.destroyed) { self.objectRef.table.destroy(self.objectRef.ptr, core.get(GameObjectSystem).allocator); self.destroyed = true; } } }; pub const SpawnParameters = struct { posRot: core.ScenePosRot = .{}, scale: core.Vectorf = .{ .z = 1.0, .y = 1.0, .x = 1.0 }, }; pub const SpawnEvent = struct { interface: GameObjectInterfaceVTable, }; pub const GameObjectSystem = struct { // this is the new one, GameObjectList should be deleted after this passes initial usability pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "core.GameObjectSystem"); allocator: std.mem.Allocator = undefined, objectDefinitions: std.AutoHashMapUnmanaged(u32, GameObjectInterfaceVTable) = .{}, typesArena: std.heap.ArenaAllocator = undefined, objectSpawnEvents: std.ArrayListUnmanaged(SpawnEvent) = .{}, pub fn create(self: *@This(), allocator: std.mem.Allocator, first: bool) !void { if (!first) return; self.* = .{ .allocator = allocator, .typesArena = std.heap.ArenaAllocator.init(self.allocator), }; } pub fn spawnObject(self: *@This(), comptime T: type, objectName: []const u8, parameters: SpawnParameters) !*T { var n = core.MakeName(objectName); return self.spawnObjectByName(T, &n, parameters); } pub fn spawnObjectByName(self: *@This(), comptime T: type, objectName: *core.Name, parameters: SpawnParameters) !*T { return @ptrCast(@alignCast((try self.spawnObjectFromTableByName(objectName, parameters)).ptr)); } pub fn spawnObjectFromTableByName(self: *@This(), objectName: *core.Name, parameters: SpawnParameters) !SpawnObjectResult { const interface: *GameObjectInterfaceVTable = self.objectDefinitions.getPtr(objectName.handle()) orelse { return ObjectError.UnknownObject; }; const entity = try core.createEntity(); const objectComponent = entity.addComponent(GameObject).?; const rv = SpawnObjectResult{ .ptr = try interface.create(self.allocator, entity, parameters), .entity = entity }; objectComponent.objectRef = .{ .table = interface, .ptr = rv.ptr, }; return rv; } // registers an object for spawning with the object name pub fn registerObject(self: *@This(), objectName: []const u8, comptime T: type) !void { try self.registerObjectAdvanced(objectName, T, "create"); } pub fn registerObjectAdvanced(self: *@This(), name: []const u8, comptime T: type, comptime createFunctionName: []const u8) !void { var n = core.MakeName(name); const Wrap = struct { pub fn create(allocator: std.mem.Allocator, entity: core.Entity, parameters: SpawnParameters) ObjectError!*anyopaque { return @field(T, createFunctionName)(allocator, entity, parameters) catch { return ObjectError.BadInit; }; } pub fn destroy(p: *anyopaque, alloc: std.mem.Allocator) void { T.destroy(@ptrCast(@alignCast(p)), alloc); } pub fn tick(p: *anyopaque, dt: f64) void { T.tick(@ptrCast(@alignCast(p)), dt); } }; var interface = GameObjectInterfaceVTable{ .create = Wrap.create, .tick = if (@hasDecl(T, "tick")) Wrap.tick else null, .destroy = Wrap.destroy, .objectTypeName = core.MakeTypeName(T), .objectBaseName = n, .messageHandlers = .{}, }; if (@hasDecl(T, "MessageHandlers")) { inline for (T.MessageHandlers) |handlerName| { var messageHandlerName = core.MakeName(handlerName); // const handlerFunc = @field(T, handlerName); // const handlerTypeInfo = @typeInfo(@TypeOf(handlerFunc)); // const M = @typeInfo(handlerTypeInfo.@"fn".params[3].type.?).pointer.child; const HandlerWrap = struct { pub fn messageHandler(p: *anyopaque, info: *const MessageInfo, message: *anyopaque) void { @field(T, handlerName)(@ptrCast(@alignCast(p)), info, @ptrCast(@alignCast(message))); } }; try interface.messageHandlers.put(self.typesArena.allocator(), messageHandlerName.handle(), HandlerWrap.messageHandler); } } try self.objectDefinitions.put(self.typesArena.allocator(), n.handle(), interface); } pub fn tick(self: *@This(), dt: f64) void { // for (self.objects.items) |object| { // if (object.vtable.tick) |tick_fn| { // tick_fn(object.ptr, dt); // } // } _ = self; _ = dt; } pub fn destroy(self: *@This()) void { self.typesArena.deinit(); } }; const core = @import("core.zig"); const std = @import("std");