diff --git a/build.zig b/build.zig index 11f6320..335a36a 100644 --- a/build.zig +++ b/build.zig @@ -27,6 +27,7 @@ const engineDepList = [_][]const u8{ "assets", "audio", "core", + "net", "papyrus", "platform", "rend", @@ -234,9 +235,9 @@ pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: [] } pub fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void { - if (self.staticBuild) { - return; - } + //if (self.staticBuild) { + //return; + // } inline for (DynamicDepList) |d| { b.installArtifact(b.dependency( @@ -257,6 +258,7 @@ const DynamicDepList: []const struct { dep: []const u8, artifact: []const u8 } = .{ .dep = "lua", .artifact = "luac" }, .{ .dep = "miniaudio", .artifact = "miniaudio_c" }, .{ .dep = "zphysics", .artifact = "joltc" }, + .{ .dep = "enet", .artifact = "enet_c" }, .{ .dep = "ozz", .artifact = "ozz_cpp" }, }; @@ -273,6 +275,7 @@ pub const moduleOrder: []const []const u8 = &.{ "sys", "assets", "platform", + "net", "physics", "audio", "rend", diff --git a/build.zig.zon b/build.zig.zon index 764fb41..bd93fac 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,6 +5,7 @@ .assets = .{ .path = "engine/assets" }, .audio = .{ .path = "engine/audio" }, .core = .{ .path = "engine/core" }, + .net = .{ .path = "engine/net" }, .papyrus = .{ .path = "engine/papyrus" }, .physics = .{ .path = "engine/physics" }, .platform = .{ .path = "engine/platform" }, @@ -19,6 +20,7 @@ .zphysics = .{ .path = "lib/zphysics" }, .sdl3 = .{ .path = "lib/sdl3" }, + .enet = .{ .path = "lib/enet" }, }, .paths = .{ "", diff --git a/build/generateApi.zig b/build/generateApi.zig index ef54118..81919a9 100644 --- a/build/generateApi.zig +++ b/build/generateApi.zig @@ -63,6 +63,10 @@ pub fn main() !void { \\ }} else {{ \\ try Struct.start_module(spec, NwArgs{{}}, allocator); \\ }} + \\ if(core.MemoryTracker.MTGet()) |_| + \\ {{ + \\ core.MemoryTracker.MTPrintStatsDelta(); + \\ }} \\ try shutdownList.append(allocator, Struct.shutdown_module); \\ try shutdownModuleNames.append(allocator, feature); \\ core.engine_logs("module started >>>> " ++ feature ++ " <<<<"); diff --git a/engine/audio/src/audio.zig b/engine/audio/src/audio.zig index bca7870..faace6a 100644 --- a/engine/audio/src/audio.zig +++ b/engine/audio/src/audio.zig @@ -4,13 +4,13 @@ const std = @import("std"); const memory = core.MemoryTracker; const soundEngine = @import("sound_engine.zig"); -pub const NeonSoundEngine = soundEngine.NeonSoundEngine; +pub const SoundEngine = soundEngine.SoundEngine; pub const sound_err = soundEngine.sound_err; pub const sound_errs = soundEngine.sound_errs; pub const sound_log = soundEngine.sound_log; pub const sound_logs = soundEngine.sound_logs; -pub var gSoundEngine: *NeonSoundEngine = undefined; +pub var gSoundEngine: *SoundEngine = undefined; pub var gSoundLoader: *soundEngine.SoundLoader = undefined; pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.mem.Allocator) !void { @@ -20,12 +20,13 @@ pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.me return; } - gSoundEngine = core.createObject(NeonSoundEngine, .{ .can_tick = true }) catch unreachable; + gSoundEngine = core.createObject(SoundEngine, .{ .can_tick = true }) catch unreachable; gSoundLoader = allocator.create(soundEngine.SoundLoader) catch unreachable; gSoundLoader.* = soundEngine.SoundLoader.init(gSoundEngine); assets.getAssets().registerLoader(gSoundLoader) catch unreachable; + _ = try core.fs().installFileBytesMount("embedded:engineTick.wav", @constCast(&engineTickWav), true); // var name = core.MakeName("s_test"); // gSoundEngine.loadSound(&name, "content/sounds/engineTick.wav", .{}) catch unreachable; @@ -37,7 +38,16 @@ pub fn shutdown_module(allocator: std.mem.Allocator) void { gSoundEngine.shutdown(); } +pub const context = core.EngineObject(SoundEngine).get; + +pub fn loadBuiltinSounds() !void { + var name = core.MakeName("engine/s_tick"); + try context().loadSound(&name, "embedded:engineTick.wav", .{}); +} + pub const Module = core.ModuleDescription{ .name = "audio", .enabledByDefault = false, }; + +const engineTickWav align(8) = @embedFile("embedded/engineTick.wav").*; diff --git a/engine/audio/src/sound_engine.zig b/engine/audio/src/sound_engine.zig index ddde9b9..579ab70 100644 --- a/engine/audio/src/sound_engine.zig +++ b/engine/audio/src/sound_engine.zig @@ -29,9 +29,9 @@ pub fn sound_errs(comptime fmt: []const u8) void { pub const SoundLoader = struct { pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from("Sound", @This()); - engine: *NeonSoundEngine, + engine: *SoundEngine, - pub fn init(engine: *NeonSoundEngine) @This() { + pub fn init(engine: *SoundEngine) @This() { return @This(){ .engine = engine, }; @@ -64,11 +64,12 @@ fn ma_res(value: anytype) !void { } } // On init, SoundEngine will spawn a -pub const NeonSoundEngine = struct { +pub const SoundEngine = struct { pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "audio.SoundEngine"); engine: *ma.ma_engine, sounds: AutoHashMapUnmanaged(u32, *ma.ma_sound), + decoders: AutoHashMapUnmanaged(u32, *ma.ma_decoder), allocator: std.mem.Allocator, volume: f32 = 1.0, @@ -77,6 +78,7 @@ pub const NeonSoundEngine = struct { self.* = @This(){ .engine = allocator.create(ma.ma_engine) catch unreachable, .sounds = .{}, + .decoders = .{}, .allocator = allocator, }; @@ -101,17 +103,31 @@ pub const NeonSoundEngine = struct { const sound = try self.allocator.create(ma.ma_sound); errdefer self.allocator.destroy(sound); - const res = ma.ma_sound_init_from_file(self.engine, fileName.ptr, 0, null, null, sound); + const engineSoundBytes = try core.fs().loadFile(fileName); + const bytes = engineSoundBytes.bytes; + + // Create a decoder from the loaded bytes + const decoder = try self.allocator.create(ma.ma_decoder); + errdefer self.allocator.destroy(decoder); + + const res_decoder = ma.ma_decoder_init_memory(bytes.ptr, bytes.len, null, decoder); + if (res_decoder != ma.MA_SUCCESS) { + sound_err("failed to initialize decoder for sound: {s}", .{soundName.utf8()}); + return error.MiniAudioError; + } + + const res = ma.ma_sound_init_from_data_source(self.engine, decoder, 0, null, sound); if (res != ma.MA_SUCCESS) { sound_err("tried loading sound: {s} failed", .{soundName.utf8()}); return error.MiniAudioError; } try self.sounds.put(self.allocator, soundName.handle(), sound); + try self.decoders.put(self.allocator, soundName.handle(), decoder); ma.ma_sound_set_volume(sound, soundParams.volume); } - pub fn playSound(self: *@This(), soundName: core.Name) !void { + pub fn playSound(self: *@This(), soundName: *core.Name) !void { const maybeSound = self.sounds.get(soundName.handle()); if (maybeSound == null) @@ -142,6 +158,14 @@ pub const NeonSoundEngine = struct { self.allocator.destroy(sound.value_ptr.*); } self.sounds.deinit(self.allocator); + + var decoder_iter = self.decoders.iterator(); + while (decoder_iter.next()) |decoder| { + _ = ma.ma_decoder_uninit(decoder.value_ptr.*); + self.allocator.destroy(decoder.value_ptr.*); + } + self.decoders.deinit(self.allocator); + ma.ma_engine_uninit(self.engine); self.allocator.destroy(self.engine); self.allocator.destroy(self); diff --git a/engine/backlog.zig b/engine/backlog.zig index 0074673..2298c6e 100644 --- a/engine/backlog.zig +++ b/engine/backlog.zig @@ -13,6 +13,7 @@ pub const platform = @import("platform").module; pub const assets = @import("assets").module; pub const rend = @import("rend").module; pub const audio = @import("audio").module; +pub const net = @import("net").module; pub const ui = @import("ui").module; pub const papyrus = @import("papyrus").module; pub const physics = @import("physics").module; diff --git a/engine/core/src/MemoryTracker.zig b/engine/core/src/MemoryTracker.zig index e8e601f..1c8d9b9 100644 --- a/engine/core/src/MemoryTracker.zig +++ b/engine/core/src/MemoryTracker.zig @@ -13,6 +13,9 @@ allocationsCount: u32 = 0, totalAllocSize: usize = 0, eventsCount: usize = 0, +untrackedAllocationsCount: u32 = 0, +untrackedAllocationsSize: usize = 0, + peakAllocations: u32 = 0, peakAllocSize: usize = 0, @@ -258,11 +261,33 @@ pub fn deinit(self: *@This()) void { } pub fn addUntrackedAllocation(self: *@This(), allocatedSize: usize) void { - self.totalAllocSize += allocatedSize; + self.lock.lock(); + self.untrackedAllocationsCount += 1; + self.untrackedAllocationsSize += allocatedSize; + + if (self.timeline) |*timeline| { + timeline.pushAlloc(self.stackCompactor.?, 0, allocatedSize); + } + + self.lock.unlock(); } pub fn removeUntrackedAllocation(self: *@This(), allocatedSize: usize) void { - self.totalAllocSize -= allocatedSize; + self.lock.lock(); + self.untrackedAllocationsCount -= 1; + self.untrackedAllocationsSize -= allocatedSize; + self.lock.unlock(); +} + +pub fn getTotalMemoryUsed(self: *@This()) usize { + return self.totalAllocSize + self.untrackedAllocationsSize + self.getTrackerUsage(); +} + +pub fn getTrackerUsage(self: *@This()) usize { + if (self.timeline) |timeline| { + return timeline.events.capacity() + self.stackCompactor.?.stackArena.usage(); + } + return 0; } var gMemTracker: ?*@This() = null; diff --git a/engine/core/src/core.zig b/engine/core/src/core.zig index 69d06f7..21b4fed 100644 --- a/engine/core/src/core.zig +++ b/engine/core/src/core.zig @@ -53,6 +53,7 @@ pub usingnamespace @import("file_dialogue.zig"); pub const panickers = @import("panickers.zig"); pub const scene = @import("scene.zig"); +pub const ScenePosRot = scene.ScenePosRot; pub const SceneSystem = scene.SceneSystem; pub const Engine = engine.Engine; @@ -116,6 +117,21 @@ pub const script = @import("script.zig"); pub const stacks = @import("stacks.zig"); pub const walkAndPrintStack = stacks.walkAndPrintStack; +pub const gameObject = @import("gameObject.zig"); + +pub const SpawnParameters = gameObject.SpawnParameters; +pub const GameObject = gameObject.GameObject; +pub const GameObjectSystem = gameObject.GameObjectSystem; +pub const MessageInfo = gameObject.MessageInfo; + +pub fn registerObject(comptime T: type, name: []const u8) !void { + try get(GameObjectSystem).registerObject(name, T); +} + +pub fn registerObjectAdvanced(comptime T: type, name: []const u8, comptime funcName: []const u8) !void { + try get(GameObjectSystem).registerObjectAdvanced(name, T, funcName); +} + pub fn fs() *PackerFS { return gPackerFS; } @@ -143,6 +159,7 @@ pub fn getSessionStamp() i64 { // a struct can be used like a list of types in this way pub const ComponentList = struct { pub const Scene = scene.Scene; + pub const GameObject = gameObject.GameObject; }; pub const ModuleStartupError = error{StartupFailed}; @@ -209,6 +226,8 @@ pub fn start_module_(map: *SpecVariantMap, args: anytype, allocator: std.mem.All try algorithm.string_pool.setup(allocator); _ = try gEngine.createObject(script_bindings.ScriptTicks, .{ .can_tick = true }); + + _ = try createObject(GameObjectSystem, .{ .can_tick = true }); _ = try inputs.initInputStack(); // components define @@ -378,6 +397,10 @@ pub fn getEngineObject(comptime T: type) ?*T { return null; } +pub fn get(comptime T: type) *T { + return EngineObject(T).get(); +} + pub fn loadModule(moduleName: []const u8, watch: bool) !void { if (watch == false) unreachable; // not implemented @@ -426,3 +449,12 @@ pub fn SlackStruct(comptime T: type, comptime SlackSize: usize) type { } }; } + +pub fn cast(comptime T: type, p: *anyopaque) T { + return @alignCast(@ptrCast(p)); +} + +pub fn MakeNameFmt(comptime fmt: []const u8, args: anytype) Name { + var makeNameBuf: [256]u8 = undefined; + return algorithm.MakeName(std.fmt.bufPrint(&makeNameBuf, fmt, args) catch unreachable); +} diff --git a/engine/core/src/ecs.zig b/engine/core/src/ecs.zig index cde16b3..eadd02e 100644 --- a/engine/core/src/ecs.zig +++ b/engine/core/src/ecs.zig @@ -125,7 +125,7 @@ pub fn destroyEntity(e: Entity) void { entityEntry.containers.deinit(registry.allocator); registry.baseSet.destroyObject(e.handle); } else { - core.engine_log("UNABLE TO DESTROY ENTITY {d}", .{e.handle.index}); + // core.engine_log("UNABLE TO DESTROY ENTITY {d}", .{e.handle.index}); } } @@ -147,7 +147,7 @@ pub fn CreateEntity_Lua(state: lua.LuaState) i32 { pub fn setup(allocator: std.mem.Allocator) !void { try ComponentRef.setupFormatBuffer(allocator); - _ = try core.createObject(EcsRegistry, .{ .can_tick = true }); + _ = try core.createObject(EcsRegistry, .{ .can_tick = true, .isCore = true }); } pub fn shutdown() void { @@ -349,8 +349,10 @@ pub const Entity = struct { }, }; - pub fn destroy(self: *@This()) void { - destroyEntity(self.*); + pub fn destroy(self: @This()) void { + //if (core.getEngineObject(EcsRegistry) != null) { + destroyEntity(self); + // } } pub fn fromHandle(handle: core.ObjectHandle) @This() { diff --git a/engine/core/src/engine.zig b/engine/core/src/engine.zig index fc7a618..8f7cd7a 100644 --- a/engine/core/src/engine.zig +++ b/engine/core/src/engine.zig @@ -159,6 +159,7 @@ pub const Engine = struct { core.engine_err("RECURSIVE OBJECT CREATION NOT ALLOWED", .{}); return error.BadInit; } + self.engineObjectLUF = self.frameNumber; // bump this every time an engine object mutation has happened self.createObjectLock = true; defer self.createObjectLock = false; @@ -173,6 +174,9 @@ pub const Engine = struct { try self.engineObjects.append(self.allocator, newObjectRef); if (vtable.singletonName) |singletonName| { + if (self.engineObjectsByName.contains(vtable.singletonName.?)) { + return error.DuplicateEngineObject; + } try self.engineObjectsByName.put(self.allocator, singletonName, newObjectRef); } diff --git a/engine/core/src/gameObject.zig b/engine/core/src/gameObject.zig index 8b13789..7cb53f9 100644 --- a/engine/core/src/gameObject.zig +++ b/engine/core/src/gameObject.zig @@ -1 +1,192 @@ +// 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.destroyed = true; + self.objectRef.table.destroy(self.objectRef.ptr, core.get(GameObjectSystem).allocator); + } + } +}; + +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, + objectDefinitions: std.AutoHashMapUnmanaged(u32, GameObjectInterfaceVTable) = .{}, + typesArena: std.heap.ArenaAllocator, + + objectSpawnEvents: std.ArrayListUnmanaged(SpawnEvent) = .{}, + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ + .allocator = allocator, + .typesArena = std.heap.ArenaAllocator.init(self.allocator), + }; + + return self; + } + + 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(); + self.allocator.destroy(self); + } +}; + +const core = @import("core.zig"); +const std = @import("std"); diff --git a/engine/core/src/inputs/inputStack.zig b/engine/core/src/inputs/inputStack.zig index f9d4078..1d77239 100644 --- a/engine/core/src/inputs/inputStack.zig +++ b/engine/core/src/inputs/inputStack.zig @@ -565,6 +565,13 @@ pub const InputStack = struct { keysDown: std.AutoHashMapUnmanaged(Key, bool) = .{}, + mousePosition: core.Vector2f = .{}, + + pub fn setMousePosition(self: *@This(), x: f32, y: f32) void { + self.mousePosition.x = x; + self.mousePosition.y = y; + } + pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.InputStack"); pub fn init(allocator: std.mem.Allocator) !*@This() { diff --git a/engine/core/src/math.zig b/engine/core/src/math.zig index 0f74acb..e974e73 100644 --- a/engine/core/src/math.zig +++ b/engine/core/src/math.zig @@ -159,6 +159,18 @@ pub fn Vector2Type(comptime T: type, comptime typeName: []const u8) type { }; } + pub inline fn negate(self: @This()) @This() { + return .{ .x = -self.x, .y = self.y }; + } + + pub inline fn cross(self: @This(), other: @This()) T { + return self.x * other.y - self.y * other.x; + } + + pub inline fn removeComponent(self: @This(), other: @This()) @This() { + return self.sub(other.fmul(self.dot(other))); + } + pub inline fn from(o: anytype) @This() { const OType: std.builtin.Type = @typeInfo(@TypeOf(o.x)); switch (@typeInfo(T)) { diff --git a/engine/core/src/scene.zig b/engine/core/src/scene.zig index 5115476..6116d4b 100644 --- a/engine/core/src/scene.zig +++ b/engine/core/src/scene.zig @@ -23,7 +23,7 @@ pub const SceneMobilityMode = enum { moveable, // sceneobject is moveable and has it's final transform updated }; -pub const SceneObjectPosRot = struct { +pub const ScenePosRot = struct { position: core.Vectorf = .{ .x = 0, .y = 0, .z = 0 }, rotation: core.Rotation = core.Rotation.init(), scale: core.Vectorf = core.Vectorf.new(1.0, 1.0, 1.0), @@ -60,7 +60,7 @@ pub const SceneObjectRepr = struct { pub const SceneObject = struct { _repr: SceneObjectRepr = .{}, // not public - posRot: SceneObjectPosRot = .{}, // position and rotation + posRot: ScenePosRot = .{}, // position and rotation settings: SceneObjectSettings = .{}, // children: ArrayListUnmanaged(core.ObjectHandle) = .{}, @@ -137,6 +137,14 @@ pub const Scene = struct { core.engine_log("handle.index = 0x{x} generation = {d} alive={any}", .{ self.handle.index, self.handle.generation, self.handle.alive }); } + pub fn setPosRot(self: @This(), newPosRot: ScenePosRot) void { + if (SceneObjectContainer.get(self.handle, .posRot)) |posRot| { + posRot.* = newPosRot; + } else { + core.engine_log("setposRot failed handle.index = 0x{x} generation = {d} alive={any}", .{ self.handle.index, self.handle.generation, self.handle.alive }); + } + } + pub fn setPosition(self: @This(), position: core.Vectorf) void { if (SceneObjectContainer.get(self.handle, .posRot)) |posRot| { posRot.*.position = position; @@ -145,7 +153,7 @@ pub const Scene = struct { } } - pub fn getPosRot(self: *@This()) *SceneObjectPosRot { + pub fn getPosRot(self: *@This()) *ScenePosRot { return SceneObjectContainer.get(self.handle, .posRot).?; } @@ -214,6 +222,8 @@ pub const Scene = struct { } } + pub fn getTickCount() u32 {} + pub fn getAndResolveTransform(self: @This()) core.Transform { const repr: *SceneObjectRepr = SceneObjectContainer.get(self.handle, ._repr).?; if (repr.lastUpdate != getSceneSystem().tickCount) { @@ -227,6 +237,12 @@ pub const Scene = struct { return SceneObjectContainer.get(self.handle, ._repr).?.transform; } + pub fn updateTransform(self: @This()) void { + const repr = SceneObjectContainer.get(self.handle, ._repr).?; + const posRot = SceneObjectContainer.get(self.handle, .posRot).?; + getSceneSystem().updateTransform(repr, posRot); + } + // you MUST clearTransfomRefUnsafe() before destroying this transform pub fn setTransformRefUnsafe(self: @This(), ref: *core.Transform) void { SceneObjectContainer.get(self.handle, ._repr).?.transformOverride = ref; @@ -288,7 +304,7 @@ pub const SceneSystem = struct { pub const FieldType = SceneObjectSet.FieldType; // internal update transform function - fn updateTransform(self: *@This(), repr: *SceneObjectRepr, posRot: *const SceneObjectPosRot) void { + fn updateTransform(self: *@This(), repr: *SceneObjectRepr, posRot: *const ScenePosRot) void { if (repr.lastUpdate == self.tickCount) { return; } @@ -385,6 +401,7 @@ pub const SceneSystem = struct { pub fn tick(self: *@This(), deltaTime: f64) void { var z = tracy.ZoneNC(@src(), "Scene System Tick", 0xAABBDD); defer z.End(); + self.updateTransforms(); _ = deltaTime; } @@ -392,7 +409,6 @@ pub const SceneSystem = struct { pub fn deinit(self: *@This()) void { self.dynamicObjects.deinit(self.allocator); self.childrenArena.deinit(); - // core.undefineComponent(Scene); Scene.SceneObjectContainer.destroy(); self.allocator.destroy(self); } diff --git a/engine/core/src/utils/gameObjectList.zig b/engine/core/src/utils/gameObjectList.zig index 535a000..8439f6e 100644 --- a/engine/core/src/utils/gameObjectList.zig +++ b/engine/core/src/utils/gameObjectList.zig @@ -1,4 +1,4 @@ -// helper, add this to your game manager class and invoke it's tick, +// 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 @@ -8,7 +8,9 @@ // // 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. +// if this were to be a bit more serious... +// +// how would i use it. pub const GameObjectInterfaceVTable = struct { destroy: *const fn (*anyopaque) void, @@ -42,6 +44,7 @@ pub const GameObjectList = struct { allocator: std.mem.Allocator, // slots might be better? allow for stable indexes? + // index pool? objects: std.ArrayListUnmanaged(GameObjectInterface) = .{}, pub fn create(allocator: std.mem.Allocator) !*@This() { diff --git a/engine/modulelist.zig b/engine/modulelist.zig index 77956d6..676e967 100644 --- a/engine/modulelist.zig +++ b/engine/modulelist.zig @@ -3,6 +3,7 @@ pub const list = [_][]const u8{ "platform", "assets", "audio", + "net", "physics", "rend", diff --git a/engine/net/build.zig b/engine/net/build.zig new file mode 100644 index 0000000..b85ab0e --- /dev/null +++ b/engine/net/build.zig @@ -0,0 +1,36 @@ +const std = @import("std"); + +const depList = [_][]const u8{ + "core", + "enet", +}; + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false; + + const mod = b.addModule("net", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/net.zig"), + }); + + for (depList) |depName| { + const dep = b.dependency(depName, .{ .target = target, .optimize = optimize, .static_build = static_build }); + + mod.addImport(depName, dep.module(depName)); + } + + const test_step = b.step("test", "run unit tests for net"); + const tests = b.addTest(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("tests/tests.zig"), + }); + + tests.root_module.addImport("net", mod); + const runArtifact = b.addRunArtifact(tests); + test_step.dependOn(&runArtifact.step); + b.installArtifact(tests); +} diff --git a/engine/net/build.zig.zon b/engine/net/build.zig.zon new file mode 100644 index 0000000..749b42b --- /dev/null +++ b/engine/net/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .net, + .version = "0.0.0", + .dependencies = .{ + .core = .{ .path = "../core" }, + .enet = .{ .path = "../../lib/enet" }, + }, + .paths = .{ + "", + }, +} diff --git a/engine/net/src/enet/EnetTransport.zig b/engine/net/src/enet/EnetTransport.zig new file mode 100644 index 0000000..ad308fc --- /dev/null +++ b/engine/net/src/enet/EnetTransport.zig @@ -0,0 +1,426 @@ +pub var TransportInterfaceVTable = net.TransportInterface.Implement(@This()); +pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.ENetTransport"); + +allocator: std.mem.Allocator, +sessions: std.ArrayListUnmanaged(*net.Session) = .{}, +deadSessions: std.ArrayListUnmanaged(*net.Session) = .{}, + +const MAX_CLIENTS = 128; +const DEFAULT_PORT = 7777; +const ConnectionTimeout = 10000; + +const UnreliableChannel = 0; +const ReliableChannel = 1; +const ChannelCount = 1; + +// to implement the new links and sessions interface. +// +const QueuedLinkData = struct { + bytes: []u8, + reliable: bool, +}; + +const ENetLinkData = struct { + peer: [*c]enet.ENetPeer, + link: *net.Link, + + // todo- split into reliable and unreliable + // also maybe move to the upper level net.Link instead of LinkData + queuedMessages: core.RingQueueU(QueuedLinkData), + + testLinkPosition: core.Vectorf = .{}, + + pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void { + self.queuedMessages.deinit(); + allocator.destroy(self); + } + + pub fn queuePacketData(self: *@This(), bytes: []const u8, reliable: bool) void { + const allocator = net.netAllocator(); + self.queuedMessages.push(.{ + .bytes = allocator.dupe(u8, bytes) catch unreachable, + .reliable = reliable, + }) catch unreachable; + } + + pub fn readDebugVector(self: *@This(), data: []const u8) void { + self.testLinkPosition = @as(*const core.Vectorf, @ptrCast(@alignCast(data.ptr))).*; + //core.engine_log("reading vector position: {d} {d} {d}", self.testLinkPosition); + } + + pub fn pushDebugLoc(self: *@This()) void { + // const x = self.testLinkPosition; + // core.engine_log("sending vector position: {d} {d} {d}", x); + + const allocator = net.netAllocator(); + self.queuedMessages.push(.{ + .bytes = allocator.dupe(u8, &@as([@sizeOf(core.Vectorf)]u8, @bitCast(self.testLinkPosition))) catch unreachable, + .reliable = true, + }) catch unreachable; + } + + // raw access to sendPacket + pub fn sendPacket(self: *@This(), bytes: []const u8, reliable: bool) void { + if (enet.enet_packet_create(bytes.ptr, bytes.len, if (reliable) enet.ENET_PACKET_FLAG_RELIABLE else 0)) |packet| { + _ = enet.enet_peer_send(self.peer, if (reliable) 0 else 1, packet); + } + } +}; + +const ENetTransport = @This(); + +const ENetSessionData = struct { + address: enet.ENetAddress, + host: [*c]enet.ENetHost, + session: *net.Session, + messageCount: u32 = 0, + + debugTick: f64 = 0.05, + + pub fn findLinkByPeer(self: *@This(), peer: [*c]enet.ENetPeer) ?*net.Link { + for (self.session.links.items) |link| { + if (getLinkData(link).peer == peer) { + return link; + } + } + return null; + } + + pub fn tick(self: *@This(), dt: f64) void { + var event: enet.ENetEvent = undefined; + + // Service the host with a 1000ms timeout + const result = enet.enet_host_service(self.host, &event, 0); + + if (result > 0) { + switch (event.type) { + enet.ENET_EVENT_TYPE_CONNECT => { + // core.engine_log("a client connected, creating link", .{}); + const welcome_msg = "msg: Welcome to ENet test server!"; + const link = core.get(ENetTransport).createLink(self.session) catch unreachable; + const linkData = getLinkData(link); + linkData.peer = event.peer; + linkData.sendPacket(welcome_msg, true); + link.linkType = .server; + }, + enet.ENET_EVENT_TYPE_RECEIVE => { + self.messageCount += 1; + const data = @as([*]u8, @ptrCast(event.packet.*.data))[0..event.packet.*.dataLength]; + // core.engine_log("Received message #{}: '{s}' echoing it back", .{ self.messageCount, data }); + + if (self.findLinkByPeer(event.peer)) |link| { + const linkData = getLinkData(link); + if (link.linkType == .server) { + linkData.queuePacketData(data, true); + } + if (link.linkType == .client) { + if (!std.mem.startsWith(u8, data, "msg:")) { + linkData.readDebugVector(data); + } + } + } + + // Destroy the received packet + enet.enet_packet_destroy(event.packet); + }, + else => {}, + } + } + + for (self.session.links.items) |link| { + const linkData = getLinkData(link); + + self.debugTick -= dt; + if (self.debugTick < 0) { + self.debugTick = 0.05; + if (link.linkType == .server) { + linkData.pushDebugLoc(); + } + } + + while (linkData.queuedMessages.pop()) |queuedData| { + linkData.sendPacket(queuedData.bytes, queuedData.reliable); + net.netAllocator().free(queuedData.bytes); + } + } + } +}; + +pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ + .allocator = allocator, + }; + + try enet_mod.initialize(); + + return self; +} + +fn createSession(self: *@This(), address: ?enet.ENetAddress) !*net.Session { + const session = try net.netAllocator().create(net.Session); + session.* = .{ + .transportName = "ENet", + .transport = .{ .vtable = TransportInterfaceVTable, .ptr = self }, + .allocator = net.netAllocator(), + }; + + const transportData = try net.netAllocator().create(ENetSessionData); + transportData.session = session; + + if (address != null) { + transportData.address = address.?; + transportData.host = enet.enet_host_create(&transportData.address, MAX_CLIENTS, 2, 0, 0) orelse { + return error.ServerStartFailed; + }; + net.log("host created on port {d}", .{address.?.port}); + } else { + transportData.host = enet.enet_host_create(null, 1, 2, 0, 0) orelse { + return error.ClientStartFailed; + }; + transportData.address = .{ + .host = 0, + .port = 0, + }; + net.log("client session created", .{}); + } + + session.transportData = transportData; + try self.sessions.append(self.allocator, session); + + return session; +} + +pub fn hostSession(self: *@This(), bindInfo: ?[]const []const u8) !*net.Session { + const port: u16 = try std.fmt.parseInt(u16, bindInfo.?[0], 0); + const session = try self.createSession(enet.ENetAddress{ + .host = enet.ENET_HOST_ANY, + .port = port, + }); + + return session; +} + +pub fn endSession(self: *@This(), session: *net.Session) void { + if (session.transportData) |transportData| { + const td = core.cast(*ENetSessionData, transportData); + enet.enet_host_destroy(td.host); + } + + for (self.sessions.items, 0..) |s, i| { + if (s == session) { + _ = self.sessions.orderedRemove(i); + return; + } + } +} + +pub fn printIp(ip: u32) void { + core.engine_log("{d}.{d}.{d}.{d}", .{ + (ip >> 0) & 0xFF, + (ip >> 8) & 0xFF, + (ip >> 16) & 0xFF, + (ip >> 24) & 0xFF, + }); +} + +pub fn parseConnectTarget(allocator: std.mem.Allocator, in: []const u8) !struct { + port: u16, + address: [:0]u8, +} { + var portStr: ?[]const u8 = null; + var base: []const u8 = in; + + var j: usize = in.len; + while (j > 0) : (j -= 1) { + const i = j - 1; + + if (in[i] == ':') { + portStr = in[j..in.len]; + base = in[0..i]; + break; + } + } + + var p: u16 = DEFAULT_PORT; + + if (portStr) |ps| { + p = try std.fmt.parseInt(u16, ps, 10); + } + + return .{ .port = p, .address = try std.fmt.allocPrintZ(allocator, "{s}", .{base}) }; +} + +pub fn createLink(self: *@This(), session: *net.Session) !*net.Link { + const newLink = try net.netAllocator().create(net.Link); + + newLink.* = .{ + .transport = .{ .vtable = TransportInterfaceVTable, .ptr = self }, + .allocator = net.netAllocator(), + }; + // errdefer newLink.destroy(); + + const linkInfo = try net.netAllocator().create(ENetLinkData); + errdefer self.allocator.destroy(linkInfo); + + linkInfo.* = .{ + .link = newLink, + .peer = null, + .queuedMessages = try core.RingQueueU(QueuedLinkData).init(net.netAllocator(), 4092), + }; + + newLink.transportData = linkInfo; + newLink.session = session; + + try session.addLink(newLink); + + return newLink; +} + +pub fn getSessionData(session: *net.Session) *ENetSessionData { + return core.cast(*ENetSessionData, session.transportData.?); +} + +pub fn getLinkData(link: *net.Link) *ENetLinkData { + return core.cast(*ENetLinkData, link.transportData.?); +} + +// creates a session and a link with that session, to the host +pub fn connect(self: *@This(), target: []const u8) !*net.Link { + const rv = try parseConnectTarget(self.allocator, target); + defer self.allocator.free(rv.address); + + // parse address to target + const session = try self.createSession(null); + errdefer { + session.skipEndSession = true; + session.destroy(); + } + + const newLink = try self.createLink(session); + newLink.linkType = .client; + const linkData = core.cast(*ENetLinkData, newLink.transportData.?); + + // Set up server address + const sessionInfo = getSessionData(session); + + // Resolve server hostname + if (enet.enet_address_set_host(&sessionInfo.address, rv.address.ptr) != 0) { + std.debug.print("Failed to resolve server address: {s}\n", .{rv.address}); + return error.AddressResolutionFailed; + } + + sessionInfo.address.port = rv.port; + + // Connect to server + linkData.peer = enet.enet_host_connect(sessionInfo.host, &sessionInfo.address, ChannelCount, 0); + if (linkData.peer == null) { + std.debug.print("Failed to create connection to server\n", .{}); + return error.ConnectionFailed; + } + + std.debug.print("connecting to server [{s}] [{d}]\n", .{ rv.address, rv.port }); + var event: enet.ENetEvent = undefined; + if (enet.enet_host_service(sessionInfo.host.?, &event, ConnectionTimeout) > 0 and event.type == enet.ENET_EVENT_TYPE_CONNECT) { + std.debug.print("Connected to server successfully!\n", .{}); + newLink.state = .connecting; + newLink.session = session; + } else { + std.debug.print("Failed to connect to server within timeout event.type {d} \n", .{event.type}); + enet.enet_peer_reset(linkData.peer); + return error.ConnectionTimeout; + } + + return newLink; +} + +pub fn endLink(self: *@This(), link: *net.Link) void { + const linkData = getLinkData(link); + + enet.enet_peer_reset(linkData.peer); + + net.netAllocator().destroy(linkData); + self.deadSessions.append(self.allocator, link.session.?) catch {}; +} + +pub fn sendMessageLink(self: *@This(), link: *net.Link, data: []const u8, reliable: bool) !void { + _ = self; + + const linkData = getLinkData(link); + linkData.queuePacketData(data, reliable); +} + +pub fn preTick(self: *@This(), dt: f64) !void { + //switch (self.peerType) { + // .server => { + // self.tickServer(dt); + // }, + // .client => { + // self.tickClient(dt); + // }, + //else => {}, + // } + + for (self.sessions.items) |session| { + const sessionInfo = getSessionData(session); + sessionInfo.tick(dt); + + // tick messages in here + } +} + +pub fn tickServer(self: *@This(), dt: f64) void { + _ = dt; + + var event: enet.ENetEvent = undefined; + + const result = enet.enet_host_service(self.host.?, &event, 0); + + if (result > 0) { + switch (event.type) { + enet.ENET_EVENT_TYPE_CONNECT => { + core.engine_log("client connected!!!!"); + }, + enet.ENET_EVENT_TYPE_RECEIVE => { + const data = @as([*]u8, @ptrCast(event.packet.*.data))[0..event.packet.*.dataLength]; + _ = data; + enet.enet_packet_destroy(event.packet); + }, + + enet.ENET_EVENT_TYPE_DISCONNECT => { + for (self.clients.items, 0..) |client, i| { + if (client.id == event.peer.*.connectID) { + _ = self.clients.swapRemove(i); + } + } + }, + + else => {}, + } + } else if (result < 0) { + net.err("Error servicing host\n", .{}); + } +} + +pub fn tickClient(self: *@This(), dt: f64) void { + _ = self; + _ = dt; +} + +pub fn destroy(self: *@This()) void { + for (self.sessions.items) |session| { + session.destroy(); + } + self.sessions.deinit(self.allocator); + self.deadSessions.deinit(self.allocator); + + enet_mod.deinitialize(); + self.allocator.destroy(self); +} + +const core = @import("core"); +const net = @import("../net.zig"); +const std = @import("std"); +const enet_mod = @import("enet"); +const enet = enet_mod.c; diff --git a/engine/net/src/net.zig b/engine/net/src/net.zig new file mode 100644 index 0000000..83cfdab --- /dev/null +++ b/engine/net/src/net.zig @@ -0,0 +1,307 @@ +const core = @import("core"); +const std = @import("std"); + +const net = @import("net.zig"); +const netEngine = @import("netEngine.zig"); + +pub const EnetTransport = netEngine.EnetTransport; +pub const NetEngine = netEngine.NetEngine; +pub const err = netEngine.net_err; +pub const errs = netEngine.net_errs; +pub const log = netEngine.net_log; +pub const logs = netEngine.net_logs; + +pub fn netAllocator() std.mem.Allocator { + return core.get(NetEngine).arenaAllocator(); +} + +pub fn start_module(spec: *core.SpecVariantMap, args: anytype, allocator: std.mem.Allocator) !void { + _ = args; + _ = spec; + _ = allocator; + + if (core.isUtility()) { + return; + } + + const engine = try core.createObject(NetEngine, .{ .can_tick = true }); + _ = try engine.initalizeTransport(netEngine.EnetTransport); +} + +pub fn shutdown_module(allocator: std.mem.Allocator) void { + core.get(NetEngine).shutdown(); + _ = allocator; +} + +pub const context = core.EngineObject(NetEngine).get; + +pub const Module = core.ModuleDescription{ + .name = "net", + .enabledByDefault = false, +}; + +pub const TransportError = error{ + UnknownStatePanic, + BadInit, + UnknownError, + OutOfMemory, + ServerStartFailed, +}; + +pub fn ip2String(allocator: std.mem.Allocator, ip: u32) ![:0]u8 { + return try std.fmt.allocPrintZ(allocator, "{d}.{d}.{d}.{d}", .{ + (ip >> 0) & 0xFF, + (ip >> 8) & 0xFF, + (ip >> 16) & 0xFF, + (ip >> 24) & 0xFF, + }); +} + +pub const TransportRef = netEngine.TransportRef; + +pub const TransportInterface = core.MakeInterface("TransportInterfaceVTable", struct { + hostSession: *const fn (*anyopaque, bindInfo: ?[]const []const u8) TransportError!*Session, + endSession: *const fn (*anyopaque, *Session) void, + + connect: *const fn (*anyopaque, target: []const u8) TransportError!*Link, + endLink: *const fn (*anyopaque, *Link) void, + + sendMessage: *const fn (*anyopaque, *Link, []const u8, bool) TransportError!void, + + // deinitialize: *const fn (*anyopaque) void, + + pub fn Implement(comptime T: type) @This() { + const Wrap = struct { + + // sessions interface + pub fn hostSession(p: *anyopaque, bindInfo: ?[]const []const u8) TransportError!*Session { + const ptr: *T = @ptrCast(@alignCast(p)); + return ptr.hostSession(bindInfo) catch return TransportError.ServerStartFailed; + } + + pub fn endSession(p: *anyopaque, session: *Session) void { + const ptr: *T = @ptrCast(@alignCast(p)); + ptr.endSession(session); + } + + // links interface + pub fn connect(p: *anyopaque, target: []const u8) TransportError!*Link { + const ptr: *T = @ptrCast(@alignCast(p)); + return ptr.connect(target) catch return TransportError.UnknownStatePanic; + } + + pub fn endLink(p: *anyopaque, link: *Link) void { + const ptr: *T = @ptrCast(@alignCast(p)); + ptr.endLink(link); + } + + pub fn sendMessageLink(p: *anyopaque, link: *Link, data: []const u8, reliable: bool) TransportError!void { + const ptr: *T = @ptrCast(@alignCast(p)); + ptr.sendMessageLink(link, data, reliable) catch return TransportError.UnknownStatePanic; + } + + // transport management + // pub fn deinitialize(p: *anyopaque) void { + // const ptr: *T = @ptrCast(@alignCast(p)); + // ptr.deinitialize(); + // } + }; + + inline for (@typeInfo(Wrap).@"struct".decls) |d| { + if (!@hasDecl(T, d.name)) { + @compileError(@typeName(T) ++ " is missing implementation of func " ++ d.name); + } + } + + return .{ + .hostSession = Wrap.hostSession, + .endSession = Wrap.endSession, + .connect = Wrap.connect, + .endLink = Wrap.endLink, + .sendMessage = Wrap.sendMessageLink, + }; + } +}); + +const NewLinkCallback = struct { + data: *anyopaque, + func: Function, + + pub const Function = *const fn (*anyopaque, *Link) void; +}; + +pub const Session = struct { + transportName: []const u8 = "Dummy", + transport: ?TransportRef = null, + transportData: ?*anyopaque = null, + allocator: std.mem.Allocator, + skipEndSession: bool = false, + + newLinkCallback: std.ArrayListUnmanaged(NewLinkCallback) = .{}, + links: std.ArrayListUnmanaged(*Link) = .{}, + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + self.* = .{ + .allocator = allocator, + }; + return self; + } + + pub fn registerNewLinkCallback(self: *@This(), data: ?*anyopaque, callback: NewLinkCallback) void { + try self.newLinkCallback.append(.{ .data = data, .func = callback }); + } + + pub fn addLink(self: *@This(), link: *Link) !void { + try self.links.append(netAllocator(), link); + link.session = self; + + for (self.newLinkCallback.items) |cb| { + cb.func(cb.data, link); + } + } + + pub fn removeLink(self: *@This(), link: *Link) void { + for (self.links.items, 0..) |l, i| { + if (l == link) { + _ = self.links.swapRemove(i); + return; + } + } + } + + pub fn destroy(self: *@This()) void { + for (self.links.items) |link| { + link.destroy(); + } + + if (self.transport) |transport| { + if (!self.skipEndSession) { + transport.vtable.endSession(transport.ptr, self); + } + } + + self.newLinkCallback.deinit(self.allocator); + self.allocator.destroy(self); + } +}; + +// end result: +// Links and sessions should be very low level implementations +// +// for the gameplay framework: +// NetEngine will own the link and sessions, and all gameplay will speak to NetEngine. + +// host side flow +// +// initialize transport -> TransportInterface +// host Session -> Session // called by NetEngine +// // session now listens for incomming connections +// +// Transport Implementation calls Session->NewLink(); +// onLinkCreated -> Link +// +// Link.sendMessage(bytes []const u8, reliable:bool); // can send messages // if you decide to listen to link, you will recieve [[all]] data +// Link.poll() -> ?[]const u8 // recieve messages from the link? (to be owned by netengine) +// +// client side flow +// +// createLink(hostIp) -> Link +// +// + +pub const LinkState = enum { + invalid, + connecting, // first state when the peer connects, + connected, // +}; + +pub const LinkType = enum { + invalid, // + server, // + client, // + peer, +}; + +pub const LinkEventType = enum { + connected, + disconnected, + kicked, +}; + +pub const LinkEvent = struct { + link: *Link, + event: LinkEventType, +}; + +pub const LinkEventCallbackFn = *const fn (?*anyopaque, LinkEvent) void; + +pub const LinkEventDelegate = struct { + func: LinkEventCallbackFn, + ptr: ?*anyopaque, +}; + +pub const Link = struct { + allocator: std.mem.Allocator, + session: ?*Session = null, + + idString: []const u8 = "Uninitialized", + state: LinkState = .invalid, + linkType: LinkType = .invalid, + callbacks: std.ArrayListUnmanaged(LinkEventDelegate) = .{}, + + id: ?u32 = null, + transportData: ?*anyopaque = null, + transport: ?TransportRef = null, + + // closes, and pointer is invalidated + pub fn destroy(self: *@This()) void { + if (self.transport) |t| { + t.vtable.endLink(t.ptr, self); + } + + if (self.session) |session| { + session.removeLink(self); + } + + self.callbacks.deinit(self.allocator); + self.allocator.destroy(self); + } + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(*@This()); + + self.* = .{ + .allocator = allocator, + }; + + return self; + } + + pub fn sendMessage(self: *@This(), bytes: []const u8, reliable: bool) !void { + if (self.transport) |t| { + try t.vtable.sendMessage(t.ptr, self, bytes, reliable); + } + } + + pub fn registerLinkEventCallback(self: *@This(), data: ?*anyopaque, cb: LinkEventCallbackFn) !void { + try self.callbacks.append(.{ + .ptr = data, + .cb = cb, + }); + } +}; + +pub fn hostSession(bindInfo: ?[]const []const u8) !*Session { + if (core.get(NetEngine).transport) |t| { + return try t.vtable.hostSession(t.ptr, bindInfo); + } + return error.NoTransportAvailable; +} + +pub fn connect(target: []const u8) !*Link { + if (core.get(NetEngine).transport) |t| { + return try t.vtable.connect(t.ptr, target); + } + return error.NoTransportAvailable; +} diff --git a/engine/net/src/netEngine.zig b/engine/net/src/netEngine.zig new file mode 100644 index 0000000..8ddf2f7 --- /dev/null +++ b/engine/net/src/netEngine.zig @@ -0,0 +1,83 @@ +const core = @import("core"); +const net = @import("net.zig"); +const std = @import("std"); +pub const EnetTransport = @import("enet/EnetTransport.zig"); + +pub fn net_log(comptime fmt: []const u8, args: anytype) void { + core.printInner("[NET ]: " ++ fmt ++ "\n", args); +} + +pub fn net_logs(comptime fmt: []const u8) void { + core.printInner("[NET ]: " ++ fmt ++ "\n", .{}); +} + +pub fn net_err(comptime fmt: []const u8, args: anytype) void { + core.printInner("[NET ]: ERROR!! " ++ fmt ++ "\n", args); +} + +pub fn net_errs(comptime fmt: []const u8) void { + core.printInner("[NET ]: ERROR!! " ++ fmt ++ "\n", .{}); +} + +pub const TransportRef = core.Reference(net.TransportInterface); + +pub const NetEngine = struct { + pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "net.NetEngine"); + + allocator: std.mem.Allocator, + arena: std.heap.ArenaAllocator, + transport: ?TransportRef = null, + + pub fn init(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = @This(){ + .allocator = allocator, + .arena = std.heap.ArenaAllocator.init(allocator), + }; + + return self; + } + + pub fn arenaAllocator(self: *@This()) std.mem.Allocator { + return self.arena.allocator(); + } + + pub fn initalizeTransport(self: *@This(), comptime T: type) !TransportRef { + const transport = try core.createObject(T, .{ .can_tick = true }); + const interface = TransportRef{ + .ptr = transport, + .vtable = T.TransportInterfaceVTable, + }; + + self.transport = interface; + + net.log("transport initialized {s}", .{@typeName(T)}); + return interface; + } + + pub fn shutdown(self: *@This()) void { + net_logs("NetEngine shutting down"); + _ = self; + } + + pub fn deinit(self: *@This()) void { + self.arena.deinit(); + self.allocator.destroy(self); + } + + pub fn tick(self: *@This(), deltaTime: f64) void { + _ = self; + _ = deltaTime; + // Network tick logic will go here + } +}; + +// responsibilities +// +// NetEngine +// - all gameobjects and high level gameplay talks to this one, and queues up messages +// +// TransportInterface +// - manages connection - startServer, stopServer, startClient, stopClient +// - diff --git a/engine/papyrus/src/Event.zig b/engine/papyrus/src/Event.zig index 5c515aa..d17a643 100644 --- a/engine/papyrus/src/Event.zig +++ b/engine/papyrus/src/Event.zig @@ -87,10 +87,11 @@ pub const HandlerError = error{ EventIgnored, }; -pub const SingleFn = *const fn (NodeHandle, ?*anyopaque) HandlerError!void; -pub const PressedFn = *const fn (NodeHandle, PressedType, ?*anyopaque) HandlerError!void; +pub const SingleFn = *const fn (*papyrus.Context, NodeHandle, ?*anyopaque) HandlerError!void; +pub const PressedFn = *const fn (*papyrus.Context, NodeHandle, PressedType, ?*anyopaque) HandlerError!void; const Listener = struct { + ctx: *papyrus.Context, node: NodeHandle, event: Type, context: ?*anyopaque, @@ -99,6 +100,7 @@ const Listener = struct { }; const PressedListener = struct { + ctx: *papyrus.Context, node: NodeHandle, keycode: Key, event: PressedType, @@ -109,6 +111,7 @@ const PressedListener = struct { pub fn installMouseOverEventAdvanced( self: *@This(), + ctx: *papyrus.Context, node: NodeHandle, event: Type, context: ?*anyopaque, @@ -118,6 +121,7 @@ pub fn installMouseOverEventAdvanced( const allocator = self.arena.allocator(); const listener: Listener = .{ + .ctx = ctx, .node = node, .event = event, .context = context, @@ -136,19 +140,20 @@ pub fn installMouseOverEventAdvanced( pub fn installMouseOverEvent( self: *@This(), + ctx: *papyrus.Context, node: NodeHandle, event: Type, context: ?*anyopaque, eventFn: SingleFn, ) !void { - try installMouseOverEventAdvanced(self, node, event, context, eventFn, false); + try installMouseOverEventAdvanced(self, ctx, node, event, context, eventFn, false); } pub fn pushMouseOverEvent(self: *@This(), node: NodeHandle, event: Type) HandlerError!void { if (self.inputEvents.get(node)) |listeners| { for (listeners.items) |listener| { if (listener.event == event) { - try listener.eventFn(node, listener.context); + try listener.eventFn(listener.ctx, node, listener.context); } } } @@ -164,8 +169,18 @@ pub fn pushPressedEvent(self: *@This(), node: NodeHandle, event: PressedType, ke } } -pub fn installOnPressedEventAdvanced(self: *@This(), node: NodeHandle, event: PressedType, keycode: Key, context: ?*anyopaque, eventFn: PressedFn, innate: bool) !void { +pub fn installOnPressedEventAdvanced( + self: *@This(), + ctx: *papyrus.Context, + node: NodeHandle, + event: PressedType, + keycode: Key, + context: ?*anyopaque, + eventFn: PressedFn, + innate: bool, +) !void { const listener: PressedListener = .{ + .ctx = ctx, .node = node, .event = event, .keycode = keycode, diff --git a/engine/papyrus/src/papyrus.zig b/engine/papyrus/src/papyrus.zig index 1c4f5d9..d99beb3 100644 --- a/engine/papyrus/src/papyrus.zig +++ b/engine/papyrus/src/papyrus.zig @@ -422,6 +422,7 @@ pub const Context = struct { debugText: std.ArrayList([]u8), debugTextCount: u32 = 0, drawDebug: bool = false, + debugMousePick: bool = false, rootNodes: std.ArrayList(NodeHandle), const debugTextMax = 32; @@ -438,19 +439,21 @@ pub const Context = struct { pub fn tickDebug(self: *@This(), deltaTime: f64) !void { _ = deltaTime; try self.pushDebugText("mouse Position: {d}, {d}", .{ self.currentCursorPosition.x, self.currentCursorPosition.y }); - if (self.mousePick.selected_node) |node| { - const n = self.getRead(node); - const layout = self._displayLayout.items[node.index]; - try self.pushDebugText("found node: {d},{d} size={d}x{d} layoutpos={d},{d} layoutsize={d},{d}", .{ - node.index, - node.generation, - n.size.x, - n.size.y, - layout.pos.x, - layout.pos.y, - layout.size.x, - layout.size.y, - }); + if (self.debugMousePick) { + if (self.mousePick.selected_node) |node| { + const n = self.getRead(node); + const layout = self._displayLayout.items[node.index]; + try self.pushDebugText("found node: {d},{d} size={d}x{d} layoutpos={d},{d} layoutsize={d},{d}", .{ + node.index, + node.generation, + n.size.x, + n.size.y, + layout.pos.x, + layout.pos.y, + layout.size.x, + layout.size.y, + }); + } } } @@ -733,11 +736,11 @@ pub const Context = struct { self.get(button).text = LocText.fromUtf8(t); } - try self.events.installMouseOverEventAdvanced(button, .mouseOver, null, NodeProperty_Button.buttonMouseOverListener, true); - try self.events.installMouseOverEventAdvanced(button, .mouseOff, null, NodeProperty_Button.buttonMouseOffListener, true); + try self.events.installMouseOverEventAdvanced(self, button, .mouseOver, null, NodeProperty_Button.buttonMouseOverListener, true); + try self.events.installMouseOverEventAdvanced(self, button, .mouseOff, null, NodeProperty_Button.buttonMouseOffListener, true); - try self.events.installOnPressedEventAdvanced(button, .onPressed, .Mouse1, null, NodeProperty_Button.buttonOnPressedEvent, true); - try self.events.installOnPressedEventAdvanced(button, .onReleased, .Mouse1, null, NodeProperty_Button.buttonOnPressedEvent, true); + try self.events.installOnPressedEventAdvanced(self, button, .onPressed, .Mouse1, null, NodeProperty_Button.buttonOnPressedEvent, true); + try self.events.installOnPressedEventAdvanced(self, button, .onReleased, .Mouse1, null, NodeProperty_Button.buttonOnPressedEvent, true); return button; } @@ -1192,6 +1195,10 @@ pub const Context = struct { try self.addDebugInfo(drawList); } + if (self.debugMousePick) { + try self.mousePick.addMousePickInfo(self, drawList); + } + for (0..drawList.items.len) |i| { switch (drawList.items[i].primitive) { .Text => |*text| { @@ -1206,13 +1213,11 @@ pub const Context = struct { const defaultHeight = 16; const sizePerLine: f32 = defaultHeight + 2; const yOffsetPerLine: f32 = defaultHeight + 1; - var yOffset: f32 = sizePerLine; + var yOffset: f32 = sizePerLine * 2; const width = defaultHeight / 2 * 120; const fontHandle = self.fontCache.defaultMonoFont.atlas.fontHandle; - try self.mousePick.addMousePickInfo(&self, drawList); - try drawList.append(.{ .node = .{}, .primitive = .{ diff --git a/engine/papyrus/src/primitives/button.zig b/engine/papyrus/src/primitives/button.zig index a5d9f20..b100a76 100644 --- a/engine/papyrus/src/primitives/button.zig +++ b/engine/papyrus/src/primitives/button.zig @@ -3,7 +3,6 @@ font: papyrus.Font, buttonStyle: ButtonStyle = .{}, disabled: bool = false, buttonState: ButtonState = .Normal, - // documentation: // Each PapyrusNode has a common set of fields, these are defined in PapyrusNode // @@ -118,24 +117,21 @@ pub fn addToDrawList(dlb: DrawListBuilder) !void { }; } -pub fn buttonMouseOverListener(node: papyrus.NodeHandle, _: ?*anyopaque) papyrus.HandlerError!void { - var ctx = papyrus.getContext(); +pub fn buttonMouseOverListener(ctx: *papyrus.Context, node: papyrus.NodeHandle, _: ?*anyopaque) papyrus.HandlerError!void { var btn = ctx.getButton(node); if (btn.disabled == false) { btn.buttonState = .Hovered; } } -pub fn buttonMouseOffListener(node: papyrus.NodeHandle, _: ?*anyopaque) papyrus.HandlerError!void { - var ctx = papyrus.getContext(); +pub fn buttonMouseOffListener(ctx: *papyrus.Context, node: papyrus.NodeHandle, _: ?*anyopaque) papyrus.HandlerError!void { var btn = ctx.getButton(node); if (btn.disabled == false) { btn.buttonState = .Normal; } } -pub fn buttonOnPressedEvent(node: papyrus.NodeHandle, eventType: papyrus.PressedType, _: ?*anyopaque) papyrus.HandlerError!void { - var ctx = papyrus.getContext(); +pub fn buttonOnPressedEvent(ctx: *papyrus.Context, node: papyrus.NodeHandle, eventType: papyrus.PressedType, _: ?*anyopaque) papyrus.HandlerError!void { var btn = ctx.getButton(node); if (eventType == .onPressed) { diff --git a/engine/physics/src/physicsSystem.zig b/engine/physics/src/physicsSystem.zig index 47c73fb..86a0d5c 100644 --- a/engine/physics/src/physicsSystem.zig +++ b/engine/physics/src/physicsSystem.zig @@ -155,6 +155,7 @@ pub const PhysicsRuntime = struct { pub fn init(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); try zphysics.init(std.heap.c_allocator, .{}); + //try zphysics.init(allocator, .{}); self.* = .{ .allocator = allocator, diff --git a/engine/platform/src/windowing.zig b/engine/platform/src/windowing.zig index 27fd8d2..6506c60 100644 --- a/engine/platform/src/windowing.zig +++ b/engine/platform/src/windowing.zig @@ -209,8 +209,13 @@ pub const PlatformInstance = struct { if (self.imguiMouseConsumed and (event.type == sdl3.events.mouse_button_down or event.type == sdl3.events.mouse_button_up)) { shouldSkip = true; } - if (!shouldSkip) + if (!shouldSkip) { inputStack.routeEvent(converted); + + if (event.type == sdl3.events.mouse_motion) { + inputStack.setMousePosition(self.cursorPos.x, self.cursorPos.y); + } + } } switch (event.type) { diff --git a/engine/rend/src/camera/CameraComponent.zig b/engine/rend/src/camera/CameraComponent.zig index 0cef385..7e164e7 100644 --- a/engine/rend/src/camera/CameraComponent.zig +++ b/engine/rend/src/camera/CameraComponent.zig @@ -119,7 +119,7 @@ pub fn resolve(self: *@This()) void { base = mul(core.zm.rotationX(self.pitch), base); base = mul(core.zm.rotationZ(self.roll), base); - var posRot: core.scene.SceneObjectPosRot = .{ + var posRot: core.scene.ScenePosRot = .{ .rotation = scene.getRotation(), .position = scene.getPosition(), }; diff --git a/engine/rend/src/particles/particles.zig b/engine/rend/src/particles/particles.zig index c7605ad..856093b 100644 --- a/engine/rend/src/particles/particles.zig +++ b/engine/rend/src/particles/particles.zig @@ -375,6 +375,7 @@ pub const ParticleEmitter = struct { self.entity = core.Entity{ .handle = handle }; self.texture = rend.getTexture(&t_whiteName).?; + core.engine_logs("wtf"); self.mesh = rend.getMeshByName(&m_quad).?; if (self.entity.fetch(core.Scene)) |scene| { diff --git a/engine/rend/src/sgpu/DebugDrawSystem.zig b/engine/rend/src/sgpu/DebugDrawSystem.zig index 80db39e..39f5e6f 100644 --- a/engine/rend/src/sgpu/DebugDrawSystem.zig +++ b/engine/rend/src/sgpu/DebugDrawSystem.zig @@ -155,7 +155,7 @@ pub fn createBuffers(self: *@This()) !void { .props = 0, }); - self.ssobUpload = self.device.createGPUTransferBuffer(&.{ + self.ssobUpload = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxObjectCount * @sizeOf(debug_vert.Scene), .props = 0, diff --git a/engine/rend/src/sgpu/ParticleRenderer.zig b/engine/rend/src/sgpu/ParticleRenderer.zig index 1c3e48c..acd8416 100644 --- a/engine/rend/src/sgpu/ParticleRenderer.zig +++ b/engine/rend/src/sgpu/ParticleRenderer.zig @@ -23,6 +23,8 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { const device = rend.context().device; + const MaxParticleCount = core.configVar(u32, "rend.particles.maxCount", 200_000); + self.ssboScene = device.createGPUBuffer(&.{ .usage = .{ .bufferusageGraphicsStorageRead = true, @@ -32,7 +34,7 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { .props = 0, }); - self.ssboSceneUpload = device.createGPUTransferBuffer(&.{ + self.ssboSceneUpload = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxParticleCount * @sizeOf(meshes_vert.Scene), .props = 0, @@ -125,8 +127,6 @@ pub fn destroy(self: *@This()) void { self.allocator.destroy(self); } -const MaxParticleCount = 10_000_000; - const assets = @import("assets"); const core = @import("core"); const std = @import("std"); diff --git a/engine/rend/src/sgpu/TextureList.zig b/engine/rend/src/sgpu/TextureList.zig index 2478a89..d1aa9e3 100644 --- a/engine/rend/src/sgpu/TextureList.zig +++ b/engine/rend/src/sgpu/TextureList.zig @@ -101,13 +101,14 @@ pub fn uploadCubeFromPaths(self: *@This(), name: core.Name, paths: []const []con try core.assert(cubeList.items[0].size.x == cubeList.items[0].size.y); } - const transferBuffer = self.device.createGPUTransferBuffer(&.{ + const transferBufferSize = textureSize * textureSize * @sizeOf(u32); + const transferBuffer = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = textureSize * textureSize * @sizeOf(u32), .props = 0, }); - defer self.device.releaseGPUTransferBuffer(transferBuffer); + defer rend.renderer.releaseGPUTransferBuffer(transferBuffer, transferBufferSize); const gtti: gpu.GPUTextureTransferInfo = .{ .transfer_buffer = transferBuffer, @@ -199,9 +200,10 @@ pub fn uploadTextureFromBytes(self: *@This(), name: *core.Name, opts: UploadText .num_levels = mipLevelCount, })); - const transferBuffer = self.device.createGPUTransferBuffer(&.{ + const transferBufferSize = opts.size.x * opts.size.y * @sizeOf(u32); + const transferBuffer = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, - .size = opts.size.x * opts.size.y * @sizeOf(u32), + .size = transferBufferSize, .props = 0, }); @@ -240,7 +242,7 @@ pub fn uploadTextureFromBytes(self: *@This(), name: *core.Name, opts: UploadText if (!cmd.submitGPUCommandBuffer()) { return error.CopyFailed; } - self.device.releaseGPUTransferBuffer(transferBuffer); + rend.renderer.releaseGPUTransferBuffer(transferBuffer, transferBufferSize); const tex = try self.allocator.create(Texture); tex.* = .{ @@ -277,9 +279,10 @@ pub fn uploadTextureFromPath(self: *@This(), name: core.Name, path: []const u8) .num_levels = mipLevelCount, })); - const transferBuffer = self.device.createGPUTransferBuffer(&.{ + const transferBufferSize = png.size.x * png.size.y * @sizeOf(u32); + const transferBuffer = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, - .size = png.size.x * png.size.y * @sizeOf(u32), + .size = transferBufferSize, .props = 0, }); @@ -318,7 +321,7 @@ pub fn uploadTextureFromPath(self: *@This(), name: core.Name, path: []const u8) if (!cmd.submitGPUCommandBuffer()) { return error.CopyFailed; } - self.device.releaseGPUTransferBuffer(transferBuffer); + rend.renderer.releaseGPUTransferBuffer(transferBuffer, transferBufferSize); const tex = try self.allocator.create(Texture); tex.* = .{ diff --git a/engine/rend/src/sgpu/mesh-pool.zig b/engine/rend/src/sgpu/mesh-pool.zig index e80e865..6536efe 100644 --- a/engine/rend/src/sgpu/mesh-pool.zig +++ b/engine/rend/src/sgpu/mesh-pool.zig @@ -14,6 +14,7 @@ pub const MeshPool = struct { device: *gpu.GPUDevice, destroyList: std.ArrayList(*gpu.GPUTransferBuffer), + destroyListSizes: std.ArrayList(usize), installedMeshes: std.AutoHashMapUnmanaged(u32, meshes.IndexedMesh) = .{}, @@ -26,6 +27,7 @@ pub const MeshPool = struct { .vertexSpans = try core.MergedSpans.init(allocator, settings.vertexCount), .meshUpdates = try core.RingQueue(rend.MeshUpdate).init(allocator, 128), .destroyList = std.ArrayList(*gpu.GPUTransferBuffer).init(allocator), + .destroyListSizes = std.ArrayList(usize).init(allocator), .device = device, }; @@ -67,11 +69,12 @@ pub const MeshPool = struct { pub fn onUploadCleanup(p: *anyopaque) void { const self: *@This() = @ptrCast(@alignCast(p)); - for (self.destroyList.items) |transferBuffer| { - self.device.releaseGPUTransferBuffer(transferBuffer); + for (self.destroyList.items, 0..) |transferBuffer, i| { + rend.renderer.releaseGPUTransferBuffer(transferBuffer, self.destroyListSizes.items[i]); } self.destroyList.clearRetainingCapacity(); + self.destroyListSizes.clearRetainingCapacity(); } pub const JointMap = std.AutoHashMapUnmanaged(u32, u32); @@ -170,8 +173,10 @@ pub const MeshPool = struct { uploadSlice: []const T, ) !core.Span { const newSpan = try mergedSpans.allocate(@intCast(uploadSlice.len)); - const upload = self.device.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = @sizeOf(T) * newSpan.size, .props = 0 }); + const uploadSize = @sizeOf(T) * newSpan.size; + const upload = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = uploadSize, .props = 0 }); try self.destroyList.append(upload); + try self.destroyListSizes.append(uploadSize); var mappedSlice: []T = undefined; mappedSlice.ptr = @ptrCast(@alignCast(self.device.mapGPUTransferBuffer(upload, false))); @@ -207,6 +212,7 @@ pub const MeshPool = struct { self.installedMeshes.deinit(self.allocator); self.destroyList.deinit(); + self.destroyListSizes.deinit(); self.indexSpans.deinit(); self.meshUpdates.deinit(); self.vertexSpans.deinit(); diff --git a/engine/rend/src/sgpu/renderer.zig b/engine/rend/src/sgpu/renderer.zig index 299554a..404e0fb 100644 --- a/engine/rend/src/sgpu/renderer.zig +++ b/engine/rend/src/sgpu/renderer.zig @@ -623,7 +623,7 @@ pub const Renderer = struct { .props = 0, }); - self.ssboSceneUpload = self.device.createGPUTransferBuffer(&.{ + self.ssboSceneUpload = createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxObjectCount * @sizeOf(meshes_vert.Scene), .props = 0, @@ -638,7 +638,7 @@ pub const Renderer = struct { .props = 0, }); - self.ssboAnimationUpload = self.device.createGPUTransferBuffer(&.{ + self.ssboAnimationUpload = createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = MaxObjectCount * @sizeOf(meshes_vert.BoneTransform), .props = 0, @@ -1284,6 +1284,16 @@ pub fn getTexture(name: *core.Name) ?*rend.Texture { return context().textureList.map.get(name.handle()); } +pub fn createGPUTransferBuffer(createInfo: *const gpu.GPUTransferBufferCreateInfo) *gpu.GPUTransferBuffer { + core.MemoryTracker.MTAddUntrackedAllocation(@intCast(createInfo.size)); + return context().device.createGPUTransferBuffer(createInfo); +} + +pub fn releaseGPUTransferBuffer(transferBuffer: *gpu.GPUTransferBuffer, size: usize) void { + core.MemoryTracker.MTRemoveAllocation(size); + context().device.releaseGPUTransferBuffer(transferBuffer); +} + pub const RendererState = struct { copyPass: ?*gpu.GPURenderPass = null, pass: ?*gpu.GPURenderPass = null, diff --git a/engine/rend/src/sgpu/ssao.zig b/engine/rend/src/sgpu/ssao.zig index fba73f4..a21b3b6 100644 --- a/engine/rend/src/sgpu/ssao.zig +++ b/engine/rend/src/sgpu/ssao.zig @@ -70,12 +70,13 @@ pub fn createNoiseTexture(self: *@This()) !void { }); self.noiseTexture = ctx.device.createGPUTexture(&tci); - const transferBuffer = ctx.device.createGPUTransferBuffer(&.{ + const transferBufferSize = textureSize * textureSize * @sizeOf(f32) * 4; + const transferBuffer = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, - .size = textureSize * textureSize * @sizeOf(f32) * 4, + .size = transferBufferSize, .props = 0, }); - defer ctx.device.releaseGPUTransferBuffer(transferBuffer); + defer rend.renderer.releaseGPUTransferBuffer(transferBuffer, transferBufferSize); const gtti: gpu.GPUTextureTransferInfo = .{ .transfer_buffer = transferBuffer, diff --git a/engine/ui/src/sgpu/papyrusSgpu.zig b/engine/ui/src/sgpu/papyrusSgpu.zig index 40b89eb..28a7971 100644 --- a/engine/ui/src/sgpu/papyrusSgpu.zig +++ b/engine/ui/src/sgpu/papyrusSgpu.zig @@ -27,6 +27,8 @@ textRenderers: std.AutoHashMapUnmanaged(*papyrus.Context, *TextRenderer) = .{}, reloadingShaders: bool = false, +lastEventsCount: u64 = 0, + const DrawCommand = union(enum(u8)) { rect: struct { ssboIndex: u32, @@ -66,7 +68,7 @@ const SsboBuffer = struct { .props = 0, }; - self.staging = device.createGPUTransferBuffer(&bci); + self.staging = rend.renderer.createGPUTransferBuffer(&bci); } core.engine_log("buffer created elementSize: {d} count: {d}", .{ elementSize, count }); @@ -129,9 +131,36 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { } pub fn tick(self: *@This(), dt: f64) void { + const inputStack = core.getInputStack(); + self.screenContext.setCursorLocation(.{ + .x = @floatCast(inputStack.mousePosition.x), + .y = @floatCast(inputStack.mousePosition.y), + }); + self.screenContext.tick(dt) catch { core.engine_errs("unable to tick papyrus"); }; + + self.screenContext.pushDebugText("fps: {d:.2} ({d:.4}ms)", .{ 1 / core.getEngine().averageFrameTime, core.getEngine().averageFrameTime }) catch {}; + + if (core.MemoryTracker.MTGet()) |tracker| { + self.screenContext.pushDebugText("memory used: {d:.4} MiB in {d} allocations {d} events per frame", .{ + @as(f64, @floatFromInt(tracker.totalAllocSize)) / 1024 / 1024, + tracker.allocationsCount, + tracker.eventsCount - self.lastEventsCount, + }) catch {}; + + self.screenContext.pushDebugText("untracked allocations: {d:.4} MiB in {d} allocations", .{ + @as(f64, @floatFromInt(tracker.untrackedAllocationsSize)) / 1024 / 1024, + tracker.untrackedAllocationsCount, + }) catch {}; + + self.screenContext.pushDebugText("total {d:.4} MiB", .{ + @as(f64, @floatFromInt(tracker.getTotalMemoryUsed())) / 1024 / 1024, + }) catch {}; + + self.lastEventsCount = tracker.eventsCount; + } } pub fn setup(self: *@This()) !void { diff --git a/engine/ui/src/sgpu/textRenderer.zig b/engine/ui/src/sgpu/textRenderer.zig index 8f32db7..a7a64bf 100644 --- a/engine/ui/src/sgpu/textRenderer.zig +++ b/engine/ui/src/sgpu/textRenderer.zig @@ -371,6 +371,10 @@ pub const TextRenderer = struct { item.destroy(self.allocator); } + for (self.deadList.items) |item| { + item.destroy(self.allocator); + } + self.geo.destroy(); self.linearBuffers.deinit(self.allocator); self.assignedBuffers.deinit(self.allocator); @@ -383,7 +387,9 @@ pub const TextMeshBuffer = struct { indexBuffer: *gpu.GPUBuffer = undefined, vertexBuffer: *gpu.GPUBuffer = undefined, indexTransferBuffer: *gpu.GPUTransferBuffer = undefined, + indexTransferBufferSize: usize, vertexTransferBuffer: *gpu.GPUTransferBuffer = undefined, + vertexTransferBufferSize: usize, indexCount: u32 = 0, isSDF: bool = true, fontHandle: u32 = 0, @@ -395,15 +401,18 @@ pub const TextMeshBuffer = struct { const vertexBufferCount: u32 = maxChars * 4; const indexBufferCount: u32 = maxChars * 6; + self.vertexTransferBufferSize = vertexBufferCount * @sizeOf(TextMeshVertex); + self.indexTransferBufferSize = indexBufferCount * @sizeOf(u32); + const ctx = rend.context(); - self.vertexTransferBuffer = ctx.device.createGPUTransferBuffer(&.{ + self.vertexTransferBuffer = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = vertexBufferCount * @sizeOf(TextMeshVertex), .props = 0, }); - self.indexTransferBuffer = ctx.device.createGPUTransferBuffer(&.{ + self.indexTransferBuffer = rend.renderer.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = indexBufferCount * @sizeOf(u32), .props = 0, @@ -429,8 +438,8 @@ pub const TextMeshBuffer = struct { ctx.device.releaseGPUBuffer(self.indexBuffer); ctx.device.releaseGPUBuffer(self.vertexBuffer); - ctx.device.releaseGPUTransferBuffer(self.indexTransferBuffer); - ctx.device.releaseGPUTransferBuffer(self.vertexTransferBuffer); + rend.renderer.releaseGPUTransferBuffer(self.indexTransferBuffer, self.indexTransferBufferSize); + rend.renderer.releaseGPUTransferBuffer(self.vertexTransferBuffer, self.vertexTransferBufferSize); allocator.destroy(self); } diff --git a/extras/gameExtras/src/debuggers/RendererDebug.zig b/extras/gameExtras/src/debuggers/RendererDebug.zig index 57aae81..b05c552 100644 --- a/extras/gameExtras/src/debuggers/RendererDebug.zig +++ b/extras/gameExtras/src/debuggers/RendererDebug.zig @@ -66,6 +66,26 @@ pub fn windowOpen(entry: *igu.MenuEntry, dt: f64) void { rend.setSkyboxTexture(name.utf8()); } } + + ig.textFmt("textures", .{}) catch unreachable; + ig.separator(); + + { + var iter = rend.context().textureList.map.iterator(); + while (iter.next()) |n| { + ig.textFmt("{s}", .{n.value_ptr.*.name.utf8()}) catch unreachable; + } + } + + ig.textFmt("meshes", .{}) catch unreachable; + ig.separator(); + + { + var iter = rend.context().meshPool.installedMeshes.iterator(); + while (iter.next()) |n| { + ig.textFmt("{s}", .{n.value_ptr.*.name.utf8()}) catch unreachable; + } + } } ig.end(); } diff --git a/extras/gameExtras/src/debuggers/objectSpawner.zig b/extras/gameExtras/src/debuggers/objectSpawner.zig index acf2443..bd9686f 100644 --- a/extras/gameExtras/src/debuggers/objectSpawner.zig +++ b/extras/gameExtras/src/debuggers/objectSpawner.zig @@ -4,6 +4,7 @@ windowOpen: bool = true, spawnFuncs: std.ArrayListUnmanaged(SpawnEntry) = .{}, spawnPosition: core.Vectorf = .{}, spawnRotation: core.Rotation = .{}, +spawnTarget: ?u32 = null, pub const SpawnEntry = struct { typeName: []const u8, @@ -28,16 +29,23 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { } pub fn objectSpawnOptions(self: *@This()) void { - for (self.spawnFuncs.items) |entry| { + for (self.spawnFuncs.items, 0..) |entry, i| { if (ig.smallButton(entry.typeName.ptr)) { if (entry.spawnFunc(self.objectList)) |new| { const entity = new.vtable.getEntity.?(new.ptr); + self.spawnTarget = @intCast(i); if (!entry.opts.absoluteSpawnposition) { self.prepareEntity(entity); } } } } + + if (self.spawnTarget) |target| { + if (target >= self.spawnFuncs.items.len) { + self.spawnTarget = null; + } + } } fn spawnedObjectsList(self: *@This()) void { diff --git a/extras/gameExtras/src/debuggers/objectSystemSpawner.zig b/extras/gameExtras/src/debuggers/objectSystemSpawner.zig new file mode 100644 index 0000000..1ee44bf --- /dev/null +++ b/extras/gameExtras/src/debuggers/objectSystemSpawner.zig @@ -0,0 +1,84 @@ +allocator: std.mem.Allocator, + +selectedObjectToSpawn: ?core.Name = null, + +spawnedObjects: std.ArrayListUnmanaged(core.Entity) = .{}, + +pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "extras.ObjectSystemSpawner"); + +pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ .allocator = allocator }; + + if (core.getEngineObject(igu.TopBar)) |topbar| { + topbar.addMenuObject(self, "New Object Spawner") catch {}; + } + + return self; +} + +// spawns an arbitrary object, no type acquisition here +pub fn spawnObjectAt(self: *@This(), spawnPosRot: core.ScenePosRot) ?core.Entity { + if (self.selectedObjectToSpawn == null) + return null; + + const objectSystem: *core.GameObjectSystem = core.get(core.GameObjectSystem); + const r = objectSystem.spawnObjectFromTableByName(&self.selectedObjectToSpawn.?, .{ .posRot = spawnPosRot }) catch return null; + self.spawnedObjects.append(self.allocator, r.entity) catch return null; + return r.entity; +} + +pub fn windowOpen(entry: *igu.MenuEntry, dt: f64) void { + const self: *@This() = @ptrCast(@alignCast(entry.ctx)); + _ = dt; + + const object = core.getEngineObject(core.GameObjectSystem).?; + + if (ig.begin("ObjectSystemSpawner", null, .{})) { + if (self.selectedObjectToSpawn) |*selected| { + ig.textf("now spawning : {s}", .{selected.utf8()}); + } else { + ig.textf("click an object to spawn", .{}); + } + + var buf: [256]u8 = undefined; + var i = object.objectDefinitions.iterator(); + while (i.next()) |v| { + const x = std.fmt.bufPrintZ(&buf, "spawn##{d}", .{v.value_ptr.objectBaseName.handle()}) catch unreachable; + ig.textf("{s}", .{v.value_ptr.objectBaseName.utf8()}); + ig.sameLine(0, 10); + if (ig.smallButton(x)) { + self.selectedObjectToSpawn = v.value_ptr.objectBaseName; + } + } + + ig.separator(); + var destroyed: ?usize = null; + for (self.spawnedObjects.items, 0..) |entity, j| { + const x = std.fmt.bufPrintZ(&buf, "delete {d}", .{entity.handle.index}) catch unreachable; + if (ig.smallButton(x)) { + entity.destroy(); + destroyed = j; + } + } + + if (destroyed) |destroyedIndex| { + _ = self.spawnedObjects.orderedRemove(destroyedIndex); + } + } + + ig.end(); +} + +pub fn destroy(self: *@This()) void { + self.spawnedObjects.deinit(self.allocator); + self.allocator.destroy(self); +} + +const backlog = @import("Backlog"); +const core = backlog.core; +const rend = backlog.rend; +const ig = backlog.imgui.api; +const igu = backlog.imgui.utils; +const std = @import("std"); diff --git a/extras/gameExtras/src/gameExtras.zig b/extras/gameExtras/src/gameExtras.zig index c9984e8..33ddd16 100644 --- a/extras/gameExtras/src/gameExtras.zig +++ b/extras/gameExtras/src/gameExtras.zig @@ -1,11 +1,27 @@ pub const FpCamera = @import("gameplay/FpCamera.zig"); -pub const pawns = @import("gameplay/pawns.zig"); +pub const games = @import("gameplay/games.zig"); pub const inputDebugger = @import("debuggers/inputDebugger.zig"); pub const ObjectSpawner = @import("debuggers/objectSpawner.zig"); +pub const ObjectSystemSpawner = @import("debuggers/objectSystemSpawner.zig"); pub const RendererDebug = @import("debuggers/RendererDebug.zig"); pub const EngineTool = @import("debuggers/EngineTool.zig"); pub const PhysicsObjectList = @import("debuggers/PhysicsObjectList.zig"); pub const ParticleDebugger = @import("debuggers/ParticleDebugger.zig"); pub const browser = @import("debuggers/fileBrowser.zig"); + +pub fn setup() !void { + _ = try core.createObject(games.GameList, .{}); +} + +pub const GameModeMessage = games.GameModeMessage; +pub const GameModeState = games.GameModeState; +pub const getGame = games.getGame; +pub const beginGame = games.beginGame; +pub const endGame = games.endGame; +pub const unloadGame = games.unloadGame; + +const std = @import("std"); +const backlog = @import("Backlog"); +const core = backlog.core; diff --git a/extras/gameExtras/src/gameplay/FpCharacter.zig b/extras/gameExtras/src/gameplay/FpCharacter.zig deleted file mode 100644 index 0c16afb..0000000 --- a/extras/gameExtras/src/gameplay/FpCharacter.zig +++ /dev/null @@ -1 +0,0 @@ -// creates a diff --git a/extras/gameExtras/src/gameplay/ecsObject.zig b/extras/gameExtras/src/gameplay/ecsObject.zig deleted file mode 100644 index e69de29..0000000 diff --git a/extras/gameExtras/src/gameplay/games.zig b/extras/gameExtras/src/gameplay/games.zig new file mode 100644 index 0000000..15335e4 --- /dev/null +++ b/extras/gameExtras/src/gameplay/games.zig @@ -0,0 +1,121 @@ +// games are a + +pub const GameInterface = struct { + ptr: *anyopaque, + beginPlayFn: *const fn (*anyopaque) void, + endPlayFn: *const fn (*anyopaque) void, + + gameName: core.Name, + tags: std.ArrayListUnmanaged(core.Name) = .{}, + + pub fn beginPlay(self: @This()) void { + self.beginPlayFn(self.ptr); + } + + pub fn endPlay(self: @This()) void { + self.endPlayFn(self.ptr); + } +}; + +pub const GameList = struct { + pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "extras.GameList"); + + allocator: std.mem.Allocator, + + games: std.StringHashMapUnmanaged(*GameInterface) = .{}, + gamesByTag: std.AutoHashMapUnmanaged(u32, GameInterfaceList) = .{}, + gamesLinear: GameInterfaceList = .{}, + + const GameInterfaceList = std.ArrayListUnmanaged(*GameInterface); + + pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try allocator.create(@This()); + + self.* = .{ + .allocator = allocator, + }; + + return self; + } + + pub fn registerGame(self: *@This(), gameName: []const u8, p: *anyopaque, comptime InnerType: type) !void { + const interface = try self.allocator.create(GameInterface); + + interface.* = .{ + .ptr = p, + .beginPlayFn = InnerType.beginPlay, + .endPlayFn = InnerType.endPlay, + .gameName = core.MakeName(gameName), + }; + + try self.games.put(self.allocator, gameName, interface); + try self.gamesLinear.append(self.allocator, interface); + } + + pub fn setGameTag(self: *@This(), gameName: []const u8, name: []const u8) void { + var n = core.MakeName(name); + (self.gamesByTag.getOrPut(gameName) catch unreachable).append(n.handle()) catch unreachable; + } + + pub fn removeTag(self: *@This(), game: []const u8, tag: []const u8) void { + var tagName = core.MakeName(tag); + var gameName = core.MakeName(game); + + if (self.gamesByTag.getPtr(tagName.handle())) |list| { + for (list.items) |i| { + if (i.gameName.eql(&gameName)) { + list.swapRemove(i); + return; + } + } + } + } + + pub fn destroy(self: *@This()) void { + var iter = self.games.valueIterator(); + + while (iter.next()) |i| { + self.allocator.destroy(i.*); + } + + self.gamesLinear.deinit(self.allocator); + self.gamesByTag.deinit(self.allocator); + self.games.deinit(self.allocator); + self.allocator.destroy(self); + } +}; + +pub fn beginGame(name: []const u8) void { + getGame(name).beginPlay(); +} + +pub fn endGame(name: []const u8) void { + getGame(name).endPlay(); +} + +pub fn getGame(name: []const u8) *GameInterface { + core.engine_log("getting Game {s}", .{name}); + return core.EngineObject(GameList).get().games.get(name).?; +} + +pub fn addGame(name: []const u8, p: *anyopaque, comptime T: type) !void { + core.engine_log("setting Game {s}", .{name}); + try core.EngineObject(GameList).get().registerGame(name, p, T); +} + +pub const GameModeState = enum { + dead, + prepare, + playing, + paused, +}; + +pub const GameModeMessage = struct { + gameModeName: []const u8, + gameModeRef: ?*anyopaque = null, + gameModeStatus: GameModeState = .dead, +}; + +const std = @import("std"); +const backlog = @import("Backlog"); +const core = backlog.core; diff --git a/extras/gameExtras/src/gameplay/pawns.zig b/extras/gameExtras/src/gameplay/pawns.zig deleted file mode 100644 index c0a0777..0000000 --- a/extras/gameExtras/src/gameplay/pawns.zig +++ /dev/null @@ -1 +0,0 @@ -// this is an implementation of diff --git a/lib/enet/build.zig b/lib/enet/build.zig new file mode 100644 index 0000000..0a8502b --- /dev/null +++ b/lib/enet/build.zig @@ -0,0 +1,222 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false; + + // am i good to always have enet as static? + const enet = if (static_build and false) b.addStaticLibrary(.{ + .name = "enet_c", + .target = target, + .optimize = optimize, + }) else b.addSharedLibrary(.{ + .name = "enet_c", + .target = target, + .optimize = optimize, + }); + + enet.linkLibC(); + + enet.addCSourceFiles(.{ + .root = b.path("enet-1.3.18"), + .files = &.{ + "callbacks.c", + "compress.c", + "host.c", + "list.c", + "packet.c", + "peer.c", + "protocol.c", + "unix.c", + "win32.c", + }, + .flags = &.{"-DHAS_OFFSETOF=1"}, + }); + + enet.addIncludePath(b.path("enet-1.3.18/include")); + + // Platform-specific configuration + switch (target.result.os.tag) { + .windows => { + enet.linkSystemLibrary("ws2_32"); + enet.linkSystemLibrary("winmm"); + }, + .linux, .macos => { + // Unix platforms - no additional libraries needed + }, + else => {}, + } + + b.installArtifact(enet); + + const mod = b.addModule("enet", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/enet.zig"), + .link_libc = true, + }); + + mod.linkLibrary(enet); + mod.addIncludePath(b.path("enet-1.3.18/include/")); + + const test_step = b.step("test", "run unit tests for enet"); + const tests = b.addTest(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/tests.zig"), + .link_libc = true, + }); + tests.root_module.addImport("enet", mod); + tests.root_module.addIncludePath(b.path("enet-1.3.18/include/")); + const runArtifact = b.addRunArtifact(tests); + test_step.dependOn(&runArtifact.step); + + // Test server executable + const test_server = b.addExecutable(.{ + .name = "test-server", + .root_source_file = b.path("src/test_server.zig"), + .target = target, + .optimize = optimize, + }); + test_server.root_module.addImport("enet", mod); + test_server.linkLibC(); + + // Test client executable + const test_client = b.addExecutable(.{ + .name = "test-client", + .root_source_file = b.path("src/test_client.zig"), + .target = target, + .optimize = optimize, + }); + test_client.root_module.addImport("enet", mod); + test_client.linkLibC(); + + // Install test programs + const install_test_server = b.addInstallArtifact(test_server, .{}); + const install_test_client = b.addInstallArtifact(test_client, .{}); + + // Steps to run test programs individually + const run_test_server = b.addRunArtifact(test_server); + const run_test_client = b.addRunArtifact(test_client); + + const run_server_step = b.step("run-test-server", "Run the ENet test server"); + run_server_step.dependOn(&run_test_server.step); + + const run_client_step = b.step("run-test-client", "Run the ENet test client"); + run_client_step.dependOn(&run_test_client.step); + + // Loopback test that runs both server and client + const run_loopback_step = b.step("run-test-loopback", "Run ENet loopback test (server + client)"); + + // Create a custom step for the loopback test + const loopback_test = LoopbackTestStep.create(b, test_server, test_client); + run_loopback_step.dependOn(&loopback_test.step); + + // Build step for test programs + const build_tests_step = b.step("build-tests", "Build test server and client programs"); + build_tests_step.dependOn(&install_test_server.step); + build_tests_step.dependOn(&install_test_client.step); +} + +// Custom step to run server and client concurrently for loopback testing +const LoopbackTestStep = struct { + step: std.Build.Step, + builder: *std.Build, + server_exe: *std.Build.Step.Compile, + client_exe: *std.Build.Step.Compile, + + const Self = @This(); + + pub fn create(builder: *std.Build, server_exe: *std.Build.Step.Compile, client_exe: *std.Build.Step.Compile) *Self { + const self = builder.allocator.create(Self) catch @panic("OOM"); + self.* = Self{ + .step = std.Build.Step.init(.{ + .id = .custom, + .name = "loopback-test", + .owner = builder, + .makeFn = make, + }), + .builder = builder, + .server_exe = server_exe, + .client_exe = client_exe, + }; + + self.step.dependOn(&server_exe.step); + self.step.dependOn(&client_exe.step); + + return self; + } + + fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { + const self: *Self = @fieldParentPtr("step", step); + const allocator = self.builder.allocator; + + std.debug.print("Running ENet loopback test...\n", .{}); + + // Get the executable paths + const server_path = self.server_exe.getEmittedBin().getPath(self.builder); + const client_path = self.client_exe.getEmittedBin().getPath(self.builder); + + // Start the server process + std.debug.print("Starting test server...\n", .{}); + var server_process = std.process.Child.init(&[_][]const u8{server_path}, allocator); + server_process.stdout_behavior = .Pipe; + server_process.stderr_behavior = .Pipe; + + try server_process.spawn(); + defer { + _ = server_process.kill() catch {}; + } + + // Give server time to start + std.time.sleep(1 * std.time.ns_per_s); + + // Start the client process + std.debug.print("Starting test client...\n", .{}); + var client_process = std.process.Child.init(&[_][]const u8{client_path}, allocator); + client_process.stdout_behavior = .Pipe; + client_process.stderr_behavior = .Pipe; + + try client_process.spawn(); + + // Wait for client to complete + const client_result = try client_process.wait(); + + // Give server a moment to process the quit message and shut down naturally + std.time.sleep(2 * std.time.ns_per_s); + + // Check if server is still running and terminate if needed + const server_result = server_process.wait() catch { + std.debug.print("Server still running, terminating...\n", .{}); + _ = server_process.kill() catch {}; + return; + }; + + // Read and display output + if (client_process.stdout) |stdout| { + const client_output = try stdout.reader().readAllAlloc(allocator, 8192); + defer allocator.free(client_output); + std.debug.print("\n=== CLIENT OUTPUT ===\n{s}\n", .{client_output}); + } + + if (server_process.stdout) |stdout| { + const server_output = try stdout.reader().readAllAlloc(allocator, 8192); + defer allocator.free(server_output); + std.debug.print("\n=== SERVER OUTPUT ===\n{s}\n", .{server_output}); + } + + std.debug.print("\n=== LOOPBACK TEST RESULTS ===\n", .{}); + std.debug.print("Client exit code: {}\n", .{client_result}); + std.debug.print("Server exit code: {}\n", .{server_result}); + + if (client_result == .Exited and client_result.Exited == 0 and + server_result == .Exited and server_result.Exited == 0) + { + std.debug.print("Loopback test PASSED!\n", .{}); + } else { + std.debug.print("Loopback test FAILED!\n", .{}); + return error.TestFailed; + } + } +}; diff --git a/lib/enet/enet-1.3.18/.github/workflows/cmake.yml b/lib/enet/enet-1.3.18/.github/workflows/cmake.yml new file mode 100644 index 0000000..721d430 --- /dev/null +++ b/lib/enet/enet-1.3.18/.github/workflows/cmake.yml @@ -0,0 +1,21 @@ +on: [push, pull_request] + +name: CMake + +jobs: + cmake-build: + name: CMake ${{ matrix.os }} ${{ matrix.build_type }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + build_type: ["Debug", "Release"] + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{ matrix.build_type }} diff --git a/lib/enet/enet-1.3.18/.gitignore b/lib/enet/enet-1.3.18/.gitignore new file mode 100644 index 0000000..6d7cdbf --- /dev/null +++ b/lib/enet/enet-1.3.18/.gitignore @@ -0,0 +1,70 @@ +# Potential build directories +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +[Bb]in/ +[Dd]ebug/ +[Dd]ebugPublic/ +[Ll]og/ +[Ll]ogs/ +[Oo]bj/ +[Rr]elease/ +[Rr]eleases/ +[Ww][Ii][Nn]32/ +bld/ +build/ +builds/ +out/ +x64/ +x86/ + +# VS +.vs/ +.vscode/ +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json + +# CMake +_deps +CMakeCache.txt +CMakeFiles +CMakeLists.txt.user +CMakeScripts +CMakeUserPresets.json +CTestTestfile.cmake +cmake_install.cmake +compile_commands.json +install_manifest.txt + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects +*.dll +*.so +*.so.* +*.dylib + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb diff --git a/lib/enet/enet-1.3.18/CMakeLists.txt b/lib/enet/enet-1.3.18/CMakeLists.txt new file mode 100644 index 0000000..cbe06d7 --- /dev/null +++ b/lib/enet/enet-1.3.18/CMakeLists.txt @@ -0,0 +1,119 @@ +cmake_minimum_required(VERSION 2.8.12...3.20) + +project(enet) + +# The "configure" step. +include(CheckFunctionExists) +include(CheckStructHasMember) +include(CheckTypeSize) +check_function_exists("fcntl" HAS_FCNTL) +check_function_exists("poll" HAS_POLL) +check_function_exists("getaddrinfo" HAS_GETADDRINFO) +check_function_exists("getnameinfo" HAS_GETNAMEINFO) +check_function_exists("gethostbyname_r" HAS_GETHOSTBYNAME_R) +check_function_exists("gethostbyaddr_r" HAS_GETHOSTBYADDR_R) +check_function_exists("inet_pton" HAS_INET_PTON) +check_function_exists("inet_ntop" HAS_INET_NTOP) +check_c_source_compiles(" + #include + struct S { int a; double b; }; + int main() { + return (int)offsetof(struct S, b); + } +" HAS_OFFSETOF) +check_struct_has_member("struct msghdr" "msg_flags" "sys/types.h;sys/socket.h" HAS_MSGHDR_FLAGS) +set(CMAKE_EXTRA_INCLUDE_FILES "sys/types.h" "sys/socket.h") +check_type_size("socklen_t" HAS_SOCKLEN_T BUILTIN_TYPES_ONLY) +unset(CMAKE_EXTRA_INCLUDE_FILES) +if(MSVC) + add_definitions(-W3) +else() + add_definitions(-Wno-error) +endif() + +if(HAS_FCNTL) + add_definitions(-DHAS_FCNTL=1) +endif() +if(HAS_POLL) + add_definitions(-DHAS_POLL=1) +endif() +if(HAS_GETNAMEINFO) + add_definitions(-DHAS_GETNAMEINFO=1) +endif() +if(HAS_GETADDRINFO) + add_definitions(-DHAS_GETADDRINFO=1) +endif() +if(HAS_GETHOSTBYNAME_R) + add_definitions(-DHAS_GETHOSTBYNAME_R=1) +endif() +if(HAS_GETHOSTBYADDR_R) + add_definitions(-DHAS_GETHOSTBYADDR_R=1) +endif() +if(HAS_INET_PTON) + add_definitions(-DHAS_INET_PTON=1) +endif() +if(HAS_INET_NTOP) + add_definitions(-DHAS_INET_NTOP=1) +endif() +if(HAS_OFFSETOF) + add_definitions(-DHAS_OFFSETOF=1) +endif() +if(HAS_MSGHDR_FLAGS) + add_definitions(-DHAS_MSGHDR_FLAGS=1) +endif() +if(HAS_SOCKLEN_T) + add_definitions(-DHAS_SOCKLEN_T=1) +endif() + +include_directories(${PROJECT_SOURCE_DIR}/include) + +set(INCLUDE_FILES_PREFIX include/enet) +set(INCLUDE_FILES + ${INCLUDE_FILES_PREFIX}/callbacks.h + ${INCLUDE_FILES_PREFIX}/enet.h + ${INCLUDE_FILES_PREFIX}/list.h + ${INCLUDE_FILES_PREFIX}/protocol.h + ${INCLUDE_FILES_PREFIX}/time.h + ${INCLUDE_FILES_PREFIX}/types.h + ${INCLUDE_FILES_PREFIX}/unix.h + ${INCLUDE_FILES_PREFIX}/utility.h + ${INCLUDE_FILES_PREFIX}/win32.h +) + +set(SOURCE_FILES + callbacks.c + compress.c + host.c + list.c + packet.c + peer.c + protocol.c + unix.c + win32.c) + +source_group(include FILES ${INCLUDE_FILES}) +source_group(source FILES ${SOURCE_FILES}) + +if(WIN32 AND BUILD_SHARED_LIBS AND (MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")) + add_definitions(-DENET_DLL=1) + add_definitions(-DENET_BUILDING_LIB) +endif() + +add_library(enet + ${INCLUDE_FILES} + ${SOURCE_FILES} +) + +if (WIN32) + target_link_libraries(enet winmm ws2_32) +endif() + +include(GNUInstallDirs) +install(TARGETS enet + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/enet + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) diff --git a/lib/enet/enet-1.3.18/ChangeLog b/lib/enet/enet-1.3.18/ChangeLog new file mode 100644 index 0000000..0fc45d3 --- /dev/null +++ b/lib/enet/enet-1.3.18/ChangeLog @@ -0,0 +1,209 @@ +ENet 1.3.18 (April 14, 2024): + +* Packet sending performance improvements +* MTU negotiation fixes +* Checksum alignment fix +* No more dynamic initialization of checksum table +* ENET_SOCKOPT_TTL +* Other miscellaneous small improvements + +ENet 1.3.17 (November 15, 2020): + +* fixes for sender getting too far ahead of receiver that can cause instability with reliable packets + +ENet 1.3.16 (September 8, 2020): + +* fix bug in unreliable fragment queuing +* use single output queue for reliable and unreliable packets for saner ordering +* revert experimental throttle changes that were less stable than prior algorithm + +ENet 1.3.15 (April 20, 2020): + +* quicker RTT initialization +* use fractional precision for RTT calculations +* fixes for packet throttle with low RTT variance +* miscellaneous socket bug fixes + +ENet 1.3.14 (January 27, 2019): + +* bug fix for enet_peer_disconnect_later() +* use getaddrinfo and getnameinfo where available +* miscellaneous cleanups + +ENet 1.3.13 (April 30, 2015): + +* miscellaneous bug fixes +* added premake and cmake support +* miscellaneous documentation cleanups + +ENet 1.3.12 (April 24, 2014): + +* added maximumPacketSize and maximumWaitingData fields to ENetHost to limit the amount of +data waiting to be delivered on a peer (beware that the default maximumPacketSize is +32MB and should be set higher if desired as should maximumWaitingData) + +ENet 1.3.11 (December 26, 2013): + +* allow an ENetHost to connect to itself +* fixed possible bug with disconnect notifications during connect attempts +* fixed some preprocessor definition bugs + +ENet 1.3.10 (October 23, 2013); + +* doubled maximum reliable window size +* fixed RCVTIMEO/SNDTIMEO socket options and also added NODELAY + +ENet 1.3.9 (August 19, 2013): + +* added duplicatePeers option to ENetHost which can limit the number of peers from duplicate IPs +* added enet_socket_get_option() and ENET_SOCKOPT_ERROR +* added enet_host_random_seed() platform stub + +ENet 1.3.8 (June 2, 2013): + +* added enet_linked_version() for checking the linked version +* added enet_socket_get_address() for querying the local address of a socket +* silenced some debugging prints unless ENET_DEBUG is defined during compilation +* handle EINTR in enet_socket_wait() so that enet_host_service() doesn't propagate errors from signals +* optimized enet_host_bandwidth_throttle() to be less expensive for large numbers of peers + +ENet 1.3.7 (March 6, 2013): + +* added ENET_PACKET_FLAG_SENT to indicate that a packet is being freed because it has been sent +* added userData field to ENetPacket +* changed how random seed is generated on Windows to avoid import warnings +* fixed case where disconnects could be generated with no preceding connect event + +ENet 1.3.6 (December 11, 2012): + +* added support for intercept callback in ENetHost that can be used to process raw packets before ENet +* added enet_socket_shutdown() for issuing shutdown on a socket +* fixed enet_socket_connect() to not error on non-blocking connects +* fixed bug in MTU negotiation during connections + +ENet 1.3.5 (July 31, 2012): + +* fixed bug in unreliable packet fragment queuing + +ENet 1.3.4 (May 29, 2012): + +* added enet_peer_ping_interval() for configuring per-peer ping intervals +* added enet_peer_timeout() for configuring per-peer timeouts +* added protocol packet size limits + +ENet 1.3.3 (June 28, 2011): + +* fixed bug with simultaneous disconnects not dispatching events + +ENet 1.3.2 (May 31, 2011): + +* added support for unreliable packet fragmenting via the packet flag +ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT +* fixed regression in unreliable packet queuing +* added check against received port to limit some forms of IP-spoofing + +ENet 1.3.1 (February 10, 2011): + +* fixed bug in tracking of reliable data in transit +* reliable data window size now scales with the throttle +* fixed bug in fragment length calculation when checksums are used + +ENet 1.3.0 (June 5, 2010): + +* enet_host_create() now requires the channel limit to be specified as +a parameter +* enet_host_connect() now accepts a data parameter which is supplied +to the receiving receiving host in the event data field for a connect event +* added an adaptive order-2 PPM range coder as a built-in compressor option +which can be set with enet_host_compress_with_range_coder() +* added support for packet compression configurable with a callback +* improved session number handling to not rely on the packet checksum +field, saving 4 bytes per packet unless the checksum option is used +* removed the dependence on the rand callback for session number handling + +Caveats: This version is not protocol compatible with the 1.2 series or +earlier. The enet_host_connect and enet_host_create API functions require +supplying additional parameters. + +ENet 1.2.5 (June 28, 2011): + +* fixed bug with simultaneous disconnects not dispatching events + +ENet 1.2.4 (May 31, 2011): + +* fixed regression in unreliable packet queuing +* added check against received port to limit some forms of IP-spoofing + +ENet 1.2.3 (February 10, 2011): + +* fixed bug in tracking reliable data in transit + +ENet 1.2.2 (June 5, 2010): + +* checksum functionality is now enabled by setting a checksum callback +inside ENetHost instead of being a configure script option +* added totalSentData, totalSentPackets, totalReceivedData, and +totalReceivedPackets counters inside ENetHost for getting usage +statistics +* added enet_host_channel_limit() for limiting the maximum number of +channels allowed by connected peers +* now uses dispatch queues for event dispatch rather than potentially +unscalable array walking +* added no_memory callback that is called when a malloc attempt fails, +such that if no_memory returns rather than aborts (the default behavior), +then the error is propagated to the return value of the API calls +* now uses packed attribute for protocol structures on platforms with +strange alignment rules +* improved autoconf build system contributed by Nathan Brink allowing +for easier building as a shared library + +Caveats: If you were using the compile-time option that enabled checksums, +make sure to set the checksum callback inside ENetHost to enet_crc32 to +regain the old behavior. The ENetCallbacks structure has added new fields, +so make sure to clear the structure to zero before use if +using enet_initialize_with_callbacks(). + +ENet 1.2.1 (November 12, 2009): + +* fixed bug that could cause disconnect events to be dropped +* added thin wrapper around select() for portable usage +* added ENET_SOCKOPT_REUSEADDR socket option +* factored enet_socket_bind()/enet_socket_listen() out of enet_socket_create() +* added contributed Code::Blocks build file + +ENet 1.2 (February 12, 2008): + +* fixed bug in VERIFY_CONNECT acknowledgement that could cause connect +attempts to occasionally timeout +* fixed acknowledgements to check both the outgoing and sent queues +when removing acknowledged packets +* fixed accidental bit rot in the MSVC project file +* revised sequence number overflow handling to address some possible +disconnect bugs +* added enet_host_check_events() for getting only local queued events +* factored out socket option setting into enet_socket_set_option() so +that socket options are now set separately from enet_socket_create() + +Caveats: While this release is superficially protocol compatible with 1.1, +differences in the sequence number overflow handling can potentially cause +random disconnects. + +ENet 1.1 (June 6, 2007): + +* optional CRC32 just in case someone needs a stronger checksum than UDP +provides (--enable-crc32 configure option) +* the size of packet headers are half the size they used to be (so less +overhead when sending small packets) +* enet_peer_disconnect_later() that waits till all queued outgoing +packets get sent before issuing an actual disconnect +* freeCallback field in individual packets for notification of when a +packet is about to be freed +* ENET_PACKET_FLAG_NO_ALLOCATE for supplying pre-allocated data to a +packet (can be used in concert with freeCallback to support some custom +allocation schemes that the normal memory allocation callbacks would +normally not allow) +* enet_address_get_host_ip() for printing address numbers +* promoted the enet_socket_*() functions to be part of the API now +* a few stability/crash fixes + + diff --git a/lib/enet/enet-1.3.18/Doxyfile b/lib/enet/enet-1.3.18/Doxyfile new file mode 100644 index 0000000..b72cb50 --- /dev/null +++ b/lib/enet/enet-1.3.18/Doxyfile @@ -0,0 +1,2303 @@ +# Doxyfile 1.8.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "ENet" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = v1.3.18 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Reliable UDP networking library" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = YES + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = YES + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = YES + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = DoxygenLayout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = *.c *.h *.dox + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = ${CMAKE_CURRENT_SOURCE_DIR} + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = NO + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 1 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 118 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 240 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 0 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = YES + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /