diff --git a/engine/core/src/console.zig b/engine/core/src/console.zig index f199cc6..08a4057 100644 --- a/engine/core/src/console.zig +++ b/engine/core/src/console.zig @@ -36,6 +36,8 @@ pub const Console = struct { var i: usize = 0; const command = core.stringStrip(c); + core.console_log("console_eval > {s}", .{c}); + while (i < command.len) : (i += 1) { if (command[i] == ' ') { const func = command[0..i]; diff --git a/engine/core/src/core.zig b/engine/core/src/core.zig index 5fea533..8ccf984 100644 --- a/engine/core/src/core.zig +++ b/engine/core/src/core.zig @@ -2,6 +2,11 @@ const std = @import("std"); +pub const gameObjectList = @import("utils/gameObjectList.zig"); +pub const GameObjectList = gameObjectList.GameObjectList; +pub const GameObjectInterface = gameObjectList.GameObjectInterface; +pub const GameObjectData = gameObjectList.GameObjectData; + pub const inputs = @import("inputs/inputStack.zig"); pub const getInputStack = inputs.getInputStack; pub const ActionEvent = inputs.ActionEvent; @@ -156,9 +161,9 @@ pub fn shutdown_module(_: std.mem.Allocator) void { MemoryTracker.MTPrintStatsDelta(); logging.shutdownLogging(); - ecs.shutdown(); - debug_draw.shutdownDrawInterface(); + + ecs.shutdown(); algorithm.destroyNameRegistry(); gEngine.deinit(); gPackerFS.destroy(); diff --git a/engine/core/src/ecs.zig b/engine/core/src/ecs.zig index a480ec5..fc31bbf 100644 --- a/engine/core/src/ecs.zig +++ b/engine/core/src/ecs.zig @@ -115,6 +115,16 @@ pub fn createEntity() !Entity { return .{ .handle = try gEcsRegistry.baseSet.createObject(.{}) }; } +pub fn destroyEntity(e: Entity) void { + if (gEcsRegistry.baseSet.get(e.handle)) |entityEntry| { + for (entityEntry.containers.items) |ref| { + ref.vtable.destroyObject(ref.ptr, e.handle); + } + entityEntry.containers.deinit(gEcsRegistry.allocator); + gEcsRegistry.baseSet.destroyObject(e.handle); + } +} + pub fn CreateEntity_Lua(state: lua.LuaState) i32 { const ud = state.newZigUserdata(Entity) catch return 0; ud.* = createEntity() catch { @@ -255,6 +265,11 @@ pub const EcsRegistry = struct { for (self.systems.items) |ref| { ref.vtable.destroy(ref.ptr); } + + for (self.baseSet.dense.items) |*entry| { + entry.value.containers.deinit(self.allocator); + } + self.systems.deinit(self.allocator); self.tickableSystems.deinit(self.allocator); self.baseSet.deinit(); @@ -277,6 +292,7 @@ pub fn defineComponent(comptime Component: type, allocator: std.mem.Allocator) ! } pub fn undefineComponent(comptime Component: type) void { + core.engine_logs("undefining component " ++ @typeName(Component)); Component.BaseContainer.destroy(); } @@ -292,6 +308,10 @@ pub const Entity = struct { }, }; + pub fn destroy(self: *@This()) void { + destroyEntity(self.*); + } + pub fn fromHandle(handle: core.ObjectHandle) @This() { return .{ .handle = handle }; } @@ -299,6 +319,10 @@ pub const Entity = struct { pub fn addComponent(self: @This(), comptime Component: type) ?*Component { const rv = Component.BaseContainer.createWithHandleECS(self.handle); + const list = &gEcsRegistry.baseSet.get(self.handle).?.containers; + const allocator = gEcsRegistry.allocator; + list.append(allocator, getTypeContainer(Component)) catch return null; + // if (@hasDecl(Component, "init")) { // rv.init(self.handle); // } @@ -365,6 +389,7 @@ pub fn getTypeContainer(comptime T: type) EcsContainerRef { pub const EcsEntry = struct { containersCount: u32 = 0, + containers: std.ArrayListUnmanaged(EcsContainerRef) = .{}, }; pub const BaseSet = p2.SparseSet(EcsEntry); diff --git a/engine/core/src/utils/gameObjectList.zig b/engine/core/src/utils/gameObjectList.zig new file mode 100644 index 0000000..4dad885 --- /dev/null +++ b/engine/core/src/utils/gameObjectList.zig @@ -0,0 +1,131 @@ +// helper, add this to your game manager class and invoke it's tick, +// +// will automatically manage spawning and despawning entities and manage de-initialization for you +// i actually don't reccomend using this for the most part for more serious games +// but it's something that will help a lot when implementing gamejams +// +// what i would rather reccomend instead is creating components instead with SparseMap base container for most things +// +// however if you want to define prefabs, then something like this might be useful. +// +// Like i said, more geared towards gamejams and toys than serious work. + +pub const GameObjectInterfaceVTable = struct { + destroy: *const fn (*anyopaque) void, + tick: ?*const fn (*anyopaque, f64) void, + getEntity: ?*const fn (*anyopaque) core.Entity, +}; + +pub const GameObjectData = struct { + index: u32, + list: *GameObjectList, + + pub fn release(self: *@This(), outer: anytype) void { + core.engine_log("releases {d}", .{self.list.objects.items.len}); + if (self.list.objects.items.len > 1) { + self.list.objects.items[self.list.objects.items.len - 1].data.index = self.index; + } + _ = self.list.objects.swapRemove(@intCast(self.index)); + + self.list.allocator.destroy(outer); + } +}; + +pub const GameObjectInterface = struct { + vtable: *const GameObjectInterfaceVTable, + ptr: *anyopaque, + data: *GameObjectData, +}; + +pub const GameObjectList = struct { + allocator: std.mem.Allocator, + + objects: std.ArrayListUnmanaged(GameObjectInterface) = .{}, + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + self.* = .{ .allocator = allocator }; + + return self; + } + + pub const SpawnFunc = *const fn (*anyopaque) ?GameObjectInterface; + + pub fn spawnObjectFunction(comptime T: type) SpawnFunc { + const Wrapped = struct { + pub fn w_spawn(list_p: *anyopaque) ?GameObjectInterface { + const ctx: *GameObjectList = @ptrCast(@alignCast(list_p)); + + return ctx.spawnObject(T) catch null; + } + }; + + return Wrapped.w_spawn; + } + + pub fn spawnObject(self: *@This(), comptime T: type) !GameObjectInterface { + const Wrapped = struct { + pub fn w_tick(ptr: *anyopaque, dt: f64) void { + const object: *T = @ptrCast(@alignCast(ptr)); + object.tick(dt); + } + + pub fn w_getEntity(ptr: *anyopaque) core.Entity { + const object: *T = @ptrCast(@alignCast(ptr)); + return object.getEntity(); + } + + pub fn w_destroy(ptr: *anyopaque) void { + const object: *T = @ptrCast(@alignCast(ptr)); + object.destroy(); + } + + pub const VTable = GameObjectInterfaceVTable{ + .tick = if (@hasDecl(T, "tick")) w_tick else null, + .getEntity = if (@hasDecl(T, "getEntity")) w_getEntity else null, + .destroy = w_destroy, + }; + }; + + const new = try T.create(self.allocator); + new.data = .{ + .index = @intCast(self.objects.items.len), + .list = self, + }; + + const interface = GameObjectInterface{ + .vtable = &Wrapped.VTable, + .ptr = new, + .data = &new.data, + }; + + try self.objects.append(self.allocator, interface); + + return interface; + } + + pub fn newObject(self: *@This(), comptime T: type) !*T { + const interface = try self.spawnObject(T); + + return @ptrCast(@alignCast(interface.ptr)); + } + + pub fn tick(self: *@This(), dt: f64) void { + for (self.objects.items) |interface| { + if (interface.vtable.tick) |tick_fn| { + tick_fn(interface.ptr, dt); + } + } + } + + pub fn destroy(self: *@This()) void { + for (self.objects.items) |interface| { + interface.vtable.destroy(interface.ptr); + } + self.objects.deinit(self.allocator); + self.allocator.destroy(self); + } +}; + +const std = @import("std"); +const core = @import("../core.zig"); diff --git a/engine/core/tests/tests.zig b/engine/core/tests/tests.zig index e41d726..9be35ec 100644 --- a/engine/core/tests/tests.zig +++ b/engine/core/tests/tests.zig @@ -23,6 +23,7 @@ test "simple systems setup for core" { try test_loadingConfigs(); try test_consoleCommands(); + try test_gameObjects(std.testing.allocator); try memory.dumpTimeline("test-core-timeline.txt"); } @@ -47,6 +48,8 @@ fn test_loadingConfigs() !void { if (core.getConfigVar([]const u8, "platform.windowName")) |height| { core.engine_log("config string: {s}", .{height}); } + + try core.assert(core.configVar(f64, "platform.extent.height", 0) == core.getConfigVar(f64, "platform.extent.height").?); } fn test_consoleCommands() !void { @@ -81,3 +84,54 @@ fn test_consoleCommands() !void { core.console.evaluate("echo lmfao"); core.console.evaluate("foo lmfao"); } + +fn test_gameObjects(allocator: std.mem.Allocator) !void { + engine_logs("test: gameobjects... "); + + const objList = try core.GameObjectList.create(allocator); + defer objList.destroy(); + + const GameObject = struct { + data: core.GameObjectData = undefined, + entity: core.Entity, + + pub fn create(alloc: std.mem.Allocator) !*@This() { + const self = try alloc.create(@This()); + self.entity = try core.createEntity(); + _ = self.entity.addComponent(core.Scene); + + return self; + } + + pub fn getEntity(self: *@This()) core.Entity { + return self.entity; + } + + pub fn tick(self: *@This(), dt: f64) void { + _ = dt; + engine_log("gameObject ticking.... {d} {d}", .{ self.data.index, self.entity.handle.index }); + } + + pub fn destroy(self: *@This()) void { + self.entity.destroy(); + self.data.release(self); + } + }; + + const gameObject = try objList.newObject(GameObject); + + const gameObject2 = try objList.newObject(GameObject); + objList.tick(0.016); + gameObject.destroy(); + + objList.tick(0.016); + gameObject2.destroy(); + objList.tick(0.016); + + const spawnFunc = core.GameObjectList.spawnObjectFunction(GameObject); + const interface = spawnFunc(objList).?; + + core.engine_log("handle = {x}", .{interface.vtable.getEntity.?(interface.ptr).handle.index}); + + objList.tick(0.016); +} diff --git a/extras/gameExtras/src/gameExtras.zig b/extras/gameExtras/src/gameExtras.zig index 1c16184..9bc35a4 100644 --- a/extras/gameExtras/src/gameExtras.zig +++ b/extras/gameExtras/src/gameExtras.zig @@ -1,3 +1,5 @@ pub const FpCamera = @import("FpCamera.zig"); pub const inputDebugger = @import("inputDebugger.zig"); + +pub const ObjectSpawner = @import("objectSpawner.zig"); diff --git a/extras/gameExtras/src/objectSpawner.zig b/extras/gameExtras/src/objectSpawner.zig new file mode 100644 index 0000000..c7e1533 --- /dev/null +++ b/extras/gameExtras/src/objectSpawner.zig @@ -0,0 +1,60 @@ +allocator: std.mem.Allocator, +objectList: *core.GameObjectList, +windowOpen: bool = true, +spawnFuncs: std.ArrayListUnmanaged(SpawnEntry) = .{}, +spawnPosition: core.Vectorf = .{}, + +pub const SpawnEntry = struct { + typeName: []const u8, + spawnFunc: core.GameObjectList.SpawnFunc, +}; + +pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + self.* = .{ + .allocator = allocator, + .objectList = try core.GameObjectList.create(allocator), + }; + + return self; +} + +pub fn tick(self: *@This(), dt: f64) void { + self.objectList.tick(dt); + + if (self.windowOpen) { + if (ig.begin("object Spawner", null, .{})) { + for (self.spawnFuncs.items) |entry| { + if (ig.smallButton(entry.typeName.ptr)) { + if (entry.spawnFunc(self.objectList)) |new| { + const entity = new.vtable.getEntity.?(new.ptr); + self.prepareEntity(entity); + } + } + } + ig.end(); + } + } +} + +pub fn addSpawnFunction(self: *@This(), comptime name: []const u8, comptime T: type) !void { + try self.spawnFuncs.append(self.allocator, .{ .typeName = name, .spawnFunc = core.GameObjectList.spawnObjectFunction(T) }); +} + +pub fn prepareEntity(self: *@This(), entity: core.Entity) void { + if (entity.fetch(core.Scene)) |scene| { + scene.setPosition(self.spawnPosition); + } +} + +pub fn destroy(self: *@This()) void { + self.spawnFuncs.deinit(self.allocator); + self.objectList.destroy(); + self.allocator.destroy(self); +} + +const std = @import("std"); +const backlog = @import("Backlog"); +const core = backlog.core; +const rend = backlog.rend; +const ig = backlog.imgui.api; diff --git a/lib/p2/src/structures/sparse-set.zig b/lib/p2/src/structures/sparse-set.zig index 1f3f6c9..8309cb7 100644 --- a/lib/p2/src/structures/sparse-set.zig +++ b/lib/p2/src/structures/sparse-set.zig @@ -199,7 +199,7 @@ pub fn SparseMultiSetAdvanced(comptime T: type, comptime SparseSize: u32) type { return self.createObjectInternal(initValue, handle.index, handle.generation); } - pub fn destroyObject(self: *@This(), handle: SetHandle) bool { + pub fn destroyObject(self: *@This(), handle: SetHandle) false { // to destroy an object // get handle and get the dense position, swap and remove. // Then insert the tombstone value into the sparse handle @@ -225,8 +225,6 @@ pub fn SparseMultiSetAdvanced(comptime T: type, comptime SparseSize: u32) type { if (self.containerListener) |l| { l.onHandleRemoved(l.ptr, self.containerID, handle); } - - return true; } var prng = std.Random.DefaultPrng.init(0x1234); @@ -469,7 +467,7 @@ pub fn SparseSetAdvanced(comptime T: type, comptime SparseSize: u32) type { // Will fail if the handle already exists. pub fn createWithHandle(self: *@This(), handle: SetHandle, initValue: T) !ConstructResult { if (handle.index >= DefaultSparseSize) { - std.debug.print("this should never happen handle index is huge: {d}\n", .{handle.index}); + std.debug.print("this should never happen handle index is huge: {x} {d}\n", .{ handle.index, handle.index }); @panic("impossible handle"); } var currentDenseHandle = self.sparse[handle.index]; @@ -709,6 +707,7 @@ pub const EcsContainerInterface = interface.MakeInterface("EcsContainerInterface handleExists: *const fn (*const anyopaque, SetHandle) bool, get: *const fn (*const anyopaque, SetHandle) ?*anyopaque, createWithHandle: *const fn (*anyopaque, SetHandle) *anyopaque, + destroyObject: *const fn (*anyopaque, SetHandle) void, getContainerID: *const fn (*const anyopaque) u32, onRegister: *const fn (*anyopaque, u32, ContainerListener) void, evictFromRegistry: *const fn (*anyopaque) void, @@ -736,6 +735,12 @@ pub const EcsContainerInterface = interface.MakeInterface("EcsContainerInterface return @ptrCast(ptr.get(handle)); } + pub fn destroyObject(p: *anyopaque, handle: SetHandle) void { + // std.debug.print("creat with handle {p}\n", .{p}); + var ptr = @as(*TargetType, @ptrCast(@alignCast(p))); + ptr.destroyObject(handle); + } + pub fn createWithHandle(p: *anyopaque, handle: SetHandle) *anyopaque { // std.debug.print("creat with handle {p}\n", .{p}); var ptr = @as(*TargetType, @ptrCast(@alignCast(p))); @@ -769,6 +774,7 @@ pub const EcsContainerInterface = interface.MakeInterface("EcsContainerInterface .handleExists = Wrap.handleExists, .get = Wrap.get, .createWithHandle = Wrap.createWithHandle, + .destroyObject = Wrap.destroyObject, .getContainerID = Wrap.getContainerID, .onRegister = Wrap.onRegister, .evictFromRegistry = Wrap.evictFromRegistry, diff --git a/projects/sampleGame/main.zig b/projects/sampleGame/main.zig index fbe0164..496e9b0 100644 --- a/projects/sampleGame/main.zig +++ b/projects/sampleGame/main.zig @@ -20,8 +20,9 @@ doomplayer: *DoomPlayer = undefined, videoplayerObject: core.Entity = undefined, doomplayerObject: core.Entity = undefined, -inputEnabled: bool = true, +objectSpawner: *extras.ObjectSpawner = undefined, +inputEnabled: bool = true, videoFullbright: bool = false, moveLight: bool = true, @@ -47,16 +48,16 @@ const assetReferences = [_]assets.AssetImportReference{ "m_skybox", .{ .path = "meshes/skybox.obj" }, ), - // assets.MakeImportRefOptions( - // "Mesh", - // "m_fox", - // .{ .path = "gltf-samples/Fox/glTF/Fox.gltf" }, - // ), - // assets.MakeImportRefOptions( - // "Texture", - // "t_fox", - // .{ .path = "gltf-samples/Fox/glTF/Texture.png" }, - // ), + assets.MakeImportRefOptions( + "Mesh", + "m_fox", + .{ .path = "gltf-samples/Fox/glTF/Fox.gltf" }, + ), + assets.MakeImportRefOptions( + "Texture", + "t_fox", + .{ .path = "gltf-samples/Fox/glTF/Texture.png" }, + ), assets.MakeImportRefOptions( "Texture", "t_empire", @@ -79,6 +80,35 @@ const assetReferences = [_]assets.AssetImportReference{ ), }; +pub const FoxObject = struct { + data: core.GameObjectData = undefined, + entity: core.Entity, + + pub fn create(alloc: std.mem.Allocator) !*@This() { + const self = try alloc.create(@This()); + const fox = try core.createEntity(); + const scene = fox.addComponent(core.Scene).?; + + scene.setScale(0.1, 0.1, 0.1); + const mesh = fox.addComponent(rend.MeshComponent).?; + mesh.setMesh("m_fox"); + mesh.setTexture("t_fox"); + + self.entity = fox; + + return self; + } + + pub fn getEntity(self: *@This()) core.Entity { + return self.entity; + } + + pub fn destroy(self: *@This()) void { + self.entity.destroy(); + self.data.release(self); + } +}; + pub fn prepare(self: *@This()) !void { core.engine_log(">>>>>>> game prepare", .{}); var z = core.tracy.ZoneN(@src(), "PREPARING GAME"); @@ -87,6 +117,9 @@ pub fn prepare(self: *@This()) !void { try script.loadTypes("scripts"); try script.runScriptFile("scripts/prepare.lua"); + self.objectSpawner = try extras.ObjectSpawner.create(self.allocator); + try self.objectSpawner.addSpawnFunction("fox", FoxObject); + try assets.loadList(assetReferences); self.videoplayer = try VideoPlayer.create(self.allocator); try self.videoplayer.startPlayback("LAPWING2.ogv"); @@ -293,6 +326,7 @@ pub fn tick(self: *@This(), dt: f64) void { const fdt: f32 = @floatCast(dt); extras.inputDebugger.tick(); + self.objectSpawner.tick(dt); self.videoplayer.tick(dt); self.doomplayer.tick(dt); self.fpcamera.tick(dt); @@ -312,6 +346,7 @@ pub fn tick(self: *@This(), dt: f64) void { const forward = self.fpcamera.getForward(); const debugCenter = self.fpcamera.getPosition().add(forward.fmul(self.centerDist)); + self.objectSpawner.spawnPosition = debugCenter; if (self.moveLight) { core.debugSphere(debugCenter, 0.3, .{}); @@ -339,6 +374,7 @@ pub fn deinit(self: *@This()) void { self.doomplayer.destroy(); self.fpcamera.destroy(); + self.objectSpawner.destroy(); self.videoplayer.destroy(); self.allocator.destroy(self); }