Backlog/engine/core/src/gameObject.zig

186 lines
6.5 KiB
Zig

// 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 init(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 deinit(self: *@This()) void {
self.typesArena.deinit();
}
};
const core = @import("core.zig");
const std = @import("std");