add sokol, imgui, tracy
This commit is contained in:
parent
9217965ec1
commit
6bc644764f
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
zig-out
|
||||
zig-pkg
|
||||
.zig-cache
|
||||
|
||||
211
build.zig
211
build.zig
@ -4,66 +4,69 @@ pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const plugin_dynamic_enabled = b.option(bool, "plugin-dynamic", "Enable loading plugin through a dynamic library") orelse true;
|
||||
const plugin_static_enabled = b.option(bool, "plugin-static", "Enable loading plugin through a dynamic library") orelse true;
|
||||
const is_wasm = target.result.cpu.arch.isWasm();
|
||||
const is_debug = (optimize == .Debug);
|
||||
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption(bool, "plugin_dynamic", plugin_dynamic_enabled);
|
||||
build_options.addOption(bool, "plugin_static", plugin_static_enabled);
|
||||
|
||||
const plugin_api_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/plugin_api.zig"),
|
||||
});
|
||||
const has_tracy = b.option(bool, "tracy", "Tracy integration") orelse is_debug;
|
||||
const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse is_debug;
|
||||
|
||||
const exe_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize
|
||||
});
|
||||
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption(bool, "has_imgui", has_imgui);
|
||||
exe_mod.addOptions("build_options", build_options);
|
||||
exe_mod.addImport("plugin_api", plugin_api_mod);
|
||||
|
||||
const plugin_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/plugin.zig"),
|
||||
const dep_sokol = b.dependency("sokol", .{
|
||||
.target = target,
|
||||
.optimize = optimize
|
||||
.optimize = optimize,
|
||||
.with_sokol_imgui = has_imgui
|
||||
});
|
||||
plugin_mod.addImport("plugin_api", plugin_api_mod);
|
||||
if (plugin_static_enabled) {
|
||||
exe_mod.addImport("plugin", plugin_mod);
|
||||
exe_mod.linkLibrary(dep_sokol.artifact("sokol_clib"));
|
||||
exe_mod.addImport("sokol", dep_sokol.module("sokol"));
|
||||
|
||||
if (has_imgui) {
|
||||
if (b.lazyDependency("cimgui", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
})) |dep_cimgui| {
|
||||
const cimgui = b.lazyImport(@This(), "cimgui").?;
|
||||
const cimgui_conf = cimgui.getConfig(true);
|
||||
exe_mod.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name));
|
||||
dep_sokol.artifact("sokol_clib").root_module.addIncludePath(dep_cimgui.path(cimgui_conf.include_dir));
|
||||
}
|
||||
}
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
const dep_stb = b.dependency("stb", .{});
|
||||
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
|
||||
exe_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
|
||||
|
||||
const dep_tracy = b.dependency("tracy", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.tracy_enable = has_tracy,
|
||||
.tracy_only_localhost = true
|
||||
});
|
||||
if (has_tracy) {
|
||||
exe_mod.linkLibrary(dep_tracy.artifact("tracy"));
|
||||
}
|
||||
exe_mod.addImport("tracy", dep_tracy.module("tracy"));
|
||||
|
||||
if (is_wasm) {
|
||||
// TODO:
|
||||
} else {
|
||||
exe_mod.resolved_target = target;
|
||||
exe_mod.optimize = optimize;
|
||||
const exe = buildNative(b, .{
|
||||
.name = "sokol_template_v2",
|
||||
.root_module = exe_mod,
|
||||
.root_module = exe_mod
|
||||
});
|
||||
|
||||
const plugin_dynamic = b.addLibrary(.{
|
||||
.name = "plugin",
|
||||
.linkage = .dynamic,
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/plugin_dynamic.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize
|
||||
})
|
||||
});
|
||||
plugin_dynamic.root_module.addImport("plugin_api", plugin_api_mod);
|
||||
plugin_dynamic.root_module.addImport("plugin", plugin_mod);
|
||||
|
||||
const plugin_install_artifact = b.addInstallArtifact(plugin_dynamic, .{ });
|
||||
if (plugin_dynamic_enabled) {
|
||||
exe.step.dependOn(&plugin_install_artifact.step);
|
||||
}
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
{
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (plugin_dynamic_enabled) {
|
||||
run_cmd.addArg("--plugin-path");
|
||||
run_cmd.addArg(b.getInstallPath(.lib, plugin_dynamic.out_lib_filename));
|
||||
}
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
@ -72,11 +75,6 @@ pub fn build(b: *std.Build) !void {
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
{
|
||||
const run_step = b.step("build-plugin", "Build plugin");
|
||||
run_step.dependOn(&plugin_install_artifact.step);
|
||||
}
|
||||
|
||||
{
|
||||
const exe_tests = b.addTest(.{ .root_module = exe_mod, });
|
||||
const run_exe_tests = b.addRunArtifact(exe_tests);
|
||||
@ -85,3 +83,122 @@ pub fn build(b: *std.Build) !void {
|
||||
test_step.dependOn(&run_exe_tests.step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BuildNativeOptions = struct {
|
||||
name: []const u8,
|
||||
root_module: *std.Build.Module,
|
||||
win32_has_console: ?bool = null,
|
||||
win32_png_icon: ?std.Build.LazyPath = null
|
||||
};
|
||||
|
||||
fn buildNative(
|
||||
b: *std.Build,
|
||||
opts: BuildNativeOptions,
|
||||
) *std.Build.Step.Compile {
|
||||
const exe = b.addExecutable(.{
|
||||
.name = opts.name,
|
||||
.root_module = opts.root_module,
|
||||
});
|
||||
|
||||
const target = opts.root_module.resolved_target.?;
|
||||
if (target.result.os.tag == .windows) {
|
||||
const optimize = opts.root_module.optimize.?;
|
||||
const has_console = opts.win32_has_console orelse (optimize == .Debug);
|
||||
exe.subsystem = if (has_console) .Console else .Windows;
|
||||
|
||||
if (opts.win32_png_icon) |win32_png_icon| {
|
||||
const png_to_icon_tool = buildPngToIconTool(b, b.graph.host, .ReleaseSafe);
|
||||
|
||||
const png_to_icon_step = b.addRunArtifact(png_to_icon_tool);
|
||||
png_to_icon_step.addFileArg(win32_png_icon);
|
||||
const icon_file = png_to_icon_step.addOutputFileArg("icon.ico");
|
||||
|
||||
const add_icon_step = AddExecutableIcon.init(exe, icon_file);
|
||||
exe.step.dependOn(&add_icon_step.step);
|
||||
}
|
||||
}
|
||||
|
||||
return exe;
|
||||
}
|
||||
|
||||
fn buildPngToIconTool(
|
||||
b: *std.Build,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
) *std.Build.Step.Compile {
|
||||
const mod = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("tools/png-to-icon.zig"),
|
||||
});
|
||||
|
||||
const dep_stb = b.dependency("stb", .{});
|
||||
mod.addImport("stb_image", dep_stb.module("stb_image"));
|
||||
|
||||
return b.addExecutable(.{
|
||||
.name = "png-to-icon",
|
||||
.root_module = mod,
|
||||
});
|
||||
}
|
||||
|
||||
const AddExecutableIcon = struct {
|
||||
obj: *std.Build.Step.Compile,
|
||||
step: std.Build.Step,
|
||||
icon_file: std.Build.LazyPath,
|
||||
resource_file: std.Build.LazyPath,
|
||||
|
||||
fn init(obj: *std.Build.Step.Compile, icon_file: std.Build.LazyPath) *AddExecutableIcon {
|
||||
const b = obj.step.owner;
|
||||
const self = b.allocator.create(AddExecutableIcon) catch @panic("OOM");
|
||||
|
||||
self.obj = obj;
|
||||
self.step = std.Build.Step.init(.{
|
||||
.id = .custom,
|
||||
.name = "add executable icon",
|
||||
.owner = b,
|
||||
.makeFn = make
|
||||
});
|
||||
|
||||
self.icon_file = icon_file;
|
||||
icon_file.addStepDependencies(&self.step);
|
||||
|
||||
const write_files = b.addWriteFiles();
|
||||
self.resource_file = write_files.add("resource-file.rc", "");
|
||||
self.step.dependOn(&write_files.step);
|
||||
|
||||
self.obj.root_module.addWin32ResourceFile(.{
|
||||
.file = self.resource_file,
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
|
||||
const b = step.owner;
|
||||
const self: *AddExecutableIcon = @fieldParentPtr("step", step);
|
||||
|
||||
const relative_icon_path = try std.fs.path.relative(
|
||||
b.allocator,
|
||||
"",
|
||||
null,
|
||||
self.resource_file.dirname().getPath(b),
|
||||
self.icon_file.getPath(b)
|
||||
);
|
||||
std.mem.replaceScalar(u8, relative_icon_path, '\\', '/');
|
||||
|
||||
{
|
||||
const cwd = std.Io.Dir.cwd();
|
||||
const resource_file = try cwd.createFile(b.graph.io, self.resource_file.getPath(b), .{ });
|
||||
defer resource_file.close(b.graph.io);
|
||||
|
||||
var buff: [4*4096]u8 = undefined;
|
||||
var file_writer = resource_file.writer(b.graph.io, &buff);
|
||||
const w = &file_writer.interface;
|
||||
|
||||
try w.writeAll("IDI_ICON ICON \"");
|
||||
try w.writeAll(relative_icon_path);
|
||||
try w.writeAll("\"");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,7 +3,22 @@
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0xf440b4e6e51f5206, // Changing this has security and trust implications.
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.dependencies = .{},
|
||||
.dependencies = .{
|
||||
.sokol = .{
|
||||
.url = "git+https://github.com/floooh/sokol-zig.git#ba97ac4e82e36a0f6c62f7fad5dc9c2d82beadd8",
|
||||
.hash = "sokol-0.1.0-pb1HK4prNwDGx2RJFyv7VtYJubDahi19XKioPzPeMCxv",
|
||||
},
|
||||
.stb = .{
|
||||
.path = "./libs/stb",
|
||||
},
|
||||
.tracy = .{
|
||||
.path = "./libs/tracy",
|
||||
},
|
||||
.cimgui = .{
|
||||
.url = "git+https://github.com/floooh/dcimgui.git#3c2b41a9333b1195e2c2f8ce6a2d3e50660a73bf",
|
||||
.hash = "cimgui-0.1.0-44ClkR8dmgDqqnfloyLE1bUAtl4zSKTT1wxaHSEC0ikV",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
|
||||
38
libs/stb/build.zig
Normal file
38
libs/stb/build.zig
Normal file
@ -0,0 +1,38 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const stb_dependency = b.dependency("stb", .{});
|
||||
|
||||
{
|
||||
const mod_stb_image = b.addModule("stb_image", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/stb_image.zig"),
|
||||
.link_libc = true,
|
||||
});
|
||||
|
||||
mod_stb_image.addIncludePath(stb_dependency.path("."));
|
||||
mod_stb_image.addCSourceFile(.{
|
||||
.file = b.path("src/stb_image_impl.c"),
|
||||
.flags = &.{}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const mod_stb_image = b.addModule("stb_vorbis", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/stb_vorbis.zig"),
|
||||
.link_libc = true,
|
||||
});
|
||||
|
||||
mod_stb_image.addIncludePath(stb_dependency.path("."));
|
||||
mod_stb_image.addCSourceFile(.{
|
||||
.file = b.path("src/stb_vorbis_impl.c"),
|
||||
.flags = &.{}
|
||||
});
|
||||
}
|
||||
}
|
||||
17
libs/stb/build.zig.zon
Normal file
17
libs/stb/build.zig.zon
Normal file
@ -0,0 +1,17 @@
|
||||
.{
|
||||
.name = .stb_image,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0xe5d3607840482046, // Changing this has security and trust implications.
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.dependencies = .{
|
||||
.stb = .{
|
||||
.url = "git+https://github.com/nothings/stb.git#f1c79c02822848a9bed4315b12c8c8f3761e1296",
|
||||
.hash = "N-V-__8AABQ7TgCnPlp8MP4YA8znrjd6E-ZjpF1rvrS8J_2I",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
||||
31
libs/stb/src/stb_image.zig
Normal file
31
libs/stb/src/stb_image.zig
Normal file
@ -0,0 +1,31 @@
|
||||
const std = @import("std");
|
||||
|
||||
const c = @cImport({
|
||||
@cDefine("STBI_NO_STDIO", {});
|
||||
@cInclude("stb_image.h");
|
||||
});
|
||||
|
||||
const STBImage = @This();
|
||||
|
||||
rgba8_pixels: [*c]u8,
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
pub fn load(png_data: []const u8) !STBImage {
|
||||
var width: c_int = undefined;
|
||||
var height: c_int = undefined;
|
||||
const pixels = c.stbi_load_from_memory(png_data.ptr, @intCast(png_data.len), &width, &height, null, 4);
|
||||
if (pixels == null) {
|
||||
return error.InvalidPng;
|
||||
}
|
||||
|
||||
return STBImage{
|
||||
.rgba8_pixels = pixels,
|
||||
.width = @intCast(width),
|
||||
.height = @intCast(height)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const STBImage) void {
|
||||
c.stbi_image_free(self.rgba8_pixels);
|
||||
}
|
||||
3
libs/stb/src/stb_image_impl.c
Normal file
3
libs/stb/src/stb_image_impl.c
Normal file
@ -0,0 +1,3 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_STDIO
|
||||
#include "stb_image.h"
|
||||
170
libs/stb/src/stb_vorbis.zig
Normal file
170
libs/stb/src/stb_vorbis.zig
Normal file
@ -0,0 +1,170 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const log = std.log.scoped(.stb_vorbis);
|
||||
|
||||
const c = @cImport({
|
||||
@cDefine("STB_VORBIS_NO_INTEGER_CONVERSION", {});
|
||||
@cDefine("STB_VORBIS_NO_STDIO", {});
|
||||
@cDefine("STB_VORBIS_HEADER_ONLY", {});
|
||||
@cInclude("stb_vorbis.c");
|
||||
});
|
||||
|
||||
const STBVorbis = @This();
|
||||
|
||||
pub const Error = error {
|
||||
NeedMoreData,
|
||||
InvalidApiMixing,
|
||||
OutOfMemory,
|
||||
FeatureNotSupported,
|
||||
TooManyChannels,
|
||||
FileOpenFailure,
|
||||
SeekWithoutLength,
|
||||
UnexpectedEof,
|
||||
SeekInvalid,
|
||||
InvalidSetup,
|
||||
InvalidStream,
|
||||
MissingCapturePattern,
|
||||
InvalidStreamStructureVersion,
|
||||
ContinuedPacketFlagInvalid,
|
||||
IncorrectStreamSerialNumber,
|
||||
InvalidFirstPage,
|
||||
BadPacketType,
|
||||
CantFindLastPage,
|
||||
SeekFailed,
|
||||
OggSkeletonNotSupported,
|
||||
|
||||
Unknown
|
||||
};
|
||||
|
||||
fn errorFromInt(err: c_int) ?Error {
|
||||
return switch (err) {
|
||||
c.VORBIS__no_error => null,
|
||||
c.VORBIS_need_more_data => Error.NeedMoreData,
|
||||
c.VORBIS_invalid_api_mixing => Error.InvalidApiMixing,
|
||||
c.VORBIS_outofmem => Error.OutOfMemory,
|
||||
c.VORBIS_feature_not_supported => Error.FeatureNotSupported,
|
||||
c.VORBIS_too_many_channels => Error.TooManyChannels,
|
||||
c.VORBIS_file_open_failure => Error.FileOpenFailure,
|
||||
c.VORBIS_seek_without_length => Error.SeekWithoutLength,
|
||||
c.VORBIS_unexpected_eof => Error.UnexpectedEof,
|
||||
c.VORBIS_seek_invalid => Error.SeekInvalid,
|
||||
c.VORBIS_invalid_setup => Error.InvalidSetup,
|
||||
c.VORBIS_invalid_stream => Error.InvalidStream,
|
||||
c.VORBIS_missing_capture_pattern => Error.MissingCapturePattern,
|
||||
c.VORBIS_invalid_stream_structure_version => Error.InvalidStreamStructureVersion,
|
||||
c.VORBIS_continued_packet_flag_invalid => Error.ContinuedPacketFlagInvalid,
|
||||
c.VORBIS_incorrect_stream_serial_number => Error.IncorrectStreamSerialNumber,
|
||||
c.VORBIS_invalid_first_page => Error.InvalidFirstPage,
|
||||
c.VORBIS_bad_packet_type => Error.BadPacketType,
|
||||
c.VORBIS_cant_find_last_page => Error.CantFindLastPage,
|
||||
c.VORBIS_seek_failed => Error.SeekFailed,
|
||||
c.VORBIS_ogg_skeleton_not_supported => Error.OggSkeletonNotSupported,
|
||||
else => Error.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
handle: *c.stb_vorbis,
|
||||
|
||||
pub fn init(data: []const u8, alloc_buffer: []u8) Error!STBVorbis {
|
||||
const stb_vorbis_alloc: c.stb_vorbis_alloc = .{
|
||||
.alloc_buffer = alloc_buffer.ptr,
|
||||
.alloc_buffer_length_in_bytes = @intCast(alloc_buffer.len)
|
||||
};
|
||||
var error_code: c_int = -1;
|
||||
const handle = c.stb_vorbis_open_memory(
|
||||
data.ptr,
|
||||
@intCast(data.len),
|
||||
&error_code,
|
||||
&stb_vorbis_alloc
|
||||
);
|
||||
if (handle == null) {
|
||||
return errorFromInt(error_code) orelse Error.Unknown;
|
||||
}
|
||||
|
||||
return STBVorbis{
|
||||
.handle = handle.?
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getMinimumAllocBufferSize(self: STBVorbis) u32 {
|
||||
const info = self.getInfo();
|
||||
return info.setup_memory_required + @max(info.setup_temp_memory_required, info.temp_memory_required);
|
||||
}
|
||||
|
||||
fn getLastError(self: STBVorbis) ?Error {
|
||||
const error_code = c.stb_vorbis_get_error(self.handle);
|
||||
return errorFromInt(error_code);
|
||||
}
|
||||
|
||||
pub fn seek(self: STBVorbis, sample_number: u32) !void {
|
||||
const success = c.stb_vorbis_seek(self.handle, sample_number);
|
||||
if (success != 1) {
|
||||
return self.getLastError() orelse Error.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getStreamLengthInSamples(self: STBVorbis) u32 {
|
||||
return c.stb_vorbis_stream_length_in_samples(self.handle);
|
||||
}
|
||||
|
||||
pub fn getStreamLengthInSeconds(self: STBVorbis) f32 {
|
||||
return c.stb_vorbis_stream_length_in_seconds(self.handle);
|
||||
}
|
||||
|
||||
pub fn getSamples(
|
||||
self: STBVorbis,
|
||||
channels: []const [*]f32,
|
||||
max_samples_per_channel: u32
|
||||
) u32 {
|
||||
const samples_per_channel = c.stb_vorbis_get_samples_float(
|
||||
self.handle,
|
||||
@intCast(channels.len),
|
||||
@constCast(@ptrCast(channels.ptr)),
|
||||
@intCast(max_samples_per_channel)
|
||||
);
|
||||
return @intCast(samples_per_channel);
|
||||
}
|
||||
|
||||
const Frame = struct {
|
||||
channels: []const [*c]const f32,
|
||||
samples_per_channel: u32
|
||||
};
|
||||
|
||||
pub fn getFrame(self: STBVorbis) Frame {
|
||||
var output: [*c][*c]f32 = null;
|
||||
var channels: c_int = undefined;
|
||||
const samples_per_channel = c.stb_vorbis_get_frame_float(
|
||||
self.handle,
|
||||
&channels,
|
||||
&output
|
||||
);
|
||||
return Frame{
|
||||
.channels = output[0..@intCast(channels)],
|
||||
.samples_per_channel = @intCast(samples_per_channel)
|
||||
};
|
||||
}
|
||||
|
||||
pub const Info = struct {
|
||||
sample_rate: u32,
|
||||
channels: u32,
|
||||
|
||||
setup_memory_required: u32,
|
||||
setup_temp_memory_required: u32,
|
||||
temp_memory_required: u32,
|
||||
|
||||
max_frame_size: u32
|
||||
};
|
||||
|
||||
pub fn getInfo(self: STBVorbis) Info {
|
||||
const info = c.stb_vorbis_get_info(self.handle);
|
||||
return Info{
|
||||
.sample_rate = info.sample_rate,
|
||||
.channels = @intCast(info.channels),
|
||||
|
||||
.setup_memory_required = info.setup_memory_required,
|
||||
.setup_temp_memory_required = info.setup_temp_memory_required,
|
||||
.temp_memory_required = info.temp_memory_required,
|
||||
|
||||
.max_frame_size = @intCast(info.max_frame_size),
|
||||
};
|
||||
}
|
||||
3
libs/stb/src/stb_vorbis_impl.c
Normal file
3
libs/stb/src/stb_vorbis_impl.c
Normal file
@ -0,0 +1,3 @@
|
||||
#define STB_VORBIS_NO_INTEGER_CONVERSION
|
||||
#define STB_VORBIS_NO_STDIO
|
||||
#include "stb_vorbis.c"
|
||||
138
libs/tracy/build.zig
Normal file
138
libs/tracy/build.zig
Normal file
@ -0,0 +1,138 @@
|
||||
const std = @import("std");
|
||||
const digits2 = std.fmt.digits2;
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const tracy_enable = b.option(bool, "tracy_enable", "Enable profiling") orelse true;
|
||||
const tracy_on_demand = b.option(bool, "tracy_on_demand", "On-demand profiling") orelse false;
|
||||
const tracy_callstack: ?u8 = b.option(u8, "tracy_callstack", "Enforce callstack collection for tracy regions");
|
||||
const tracy_no_callstack = b.option(bool, "tracy_no_callstack", "Disable all callstack related functionality") orelse false;
|
||||
const tracy_no_callstack_inlines = b.option(bool, "tracy_no_callstack_inlines", "Disables the inline functions in callstacks") orelse false;
|
||||
const tracy_only_localhost = b.option(bool, "tracy_only_localhost", "Only listen on the localhost interface") orelse false;
|
||||
const tracy_no_broadcast = b.option(bool, "tracy_no_broadcast", "Disable client discovery by broadcast to local network") orelse false;
|
||||
const tracy_only_ipv4 = b.option(bool, "tracy_only_ipv4", "Tracy will only accept connections on IPv4 addresses (disable IPv6)") orelse false;
|
||||
const tracy_no_code_transfer = b.option(bool, "tracy_no_code_transfer", "Disable collection of source code") orelse false;
|
||||
const tracy_no_context_switch = b.option(bool, "tracy_no_context_switch", "Disable capture of context switches") orelse false;
|
||||
const tracy_no_exit = b.option(bool, "tracy_no_exit", "Client executable does not exit until all profile data is sent to server") orelse false;
|
||||
const tracy_no_sampling = b.option(bool, "tracy_no_sampling", "Disable call stack sampling") orelse false;
|
||||
const tracy_no_verify = b.option(bool, "tracy_no_verify", "Disable zone validation for C API") orelse false;
|
||||
const tracy_no_vsync_capture = b.option(bool, "tracy_no_vsync_capture", "Disable capture of hardware Vsync events") orelse false;
|
||||
const tracy_no_frame_image = b.option(bool, "tracy_no_frame_image", "Disable the frame image support and its thread") orelse false;
|
||||
// NOTE For some reason system tracing on zig projects crashes tracy, will need to investigate
|
||||
const tracy_no_system_tracing = b.option(bool, "tracy_no_system_tracing", "Disable systrace sampling") orelse true;
|
||||
const tracy_delayed_init = b.option(bool, "tracy_delayed_init", "Enable delayed initialization of the library (init on first call)") orelse false;
|
||||
const tracy_manual_lifetime = b.option(bool, "tracy_manual_lifetime", "Enable the manual lifetime management of the profile") orelse false;
|
||||
const tracy_fibers = b.option(bool, "tracy_fibers", "Enable fibers support") orelse false;
|
||||
const tracy_no_crash_handler = b.option(bool, "tracy_no_crash_handler", "Disable crash handling") orelse false;
|
||||
const tracy_timer_fallback = b.option(bool, "tracy_timer_fallback", "Use lower resolution timers") orelse false;
|
||||
const shared = b.option(bool, "shared", "Build the tracy client as a shared libary") orelse false;
|
||||
|
||||
const options = b.addOptions();
|
||||
options.addOption(bool, "tracy_enable", tracy_enable);
|
||||
options.addOption(bool, "tracy_on_demand", tracy_on_demand);
|
||||
options.addOption(?u8, "tracy_callstack", tracy_callstack);
|
||||
options.addOption(bool, "tracy_no_callstack", tracy_no_callstack);
|
||||
options.addOption(bool, "tracy_no_callstack_inlines", tracy_no_callstack_inlines);
|
||||
options.addOption(bool, "tracy_only_localhost", tracy_only_localhost);
|
||||
options.addOption(bool, "tracy_no_broadcast", tracy_no_broadcast);
|
||||
options.addOption(bool, "tracy_only_ipv4", tracy_only_ipv4);
|
||||
options.addOption(bool, "tracy_no_code_transfer", tracy_no_code_transfer);
|
||||
options.addOption(bool, "tracy_no_context_switch", tracy_no_context_switch);
|
||||
options.addOption(bool, "tracy_no_exit", tracy_no_exit);
|
||||
options.addOption(bool, "tracy_no_sampling", tracy_no_sampling);
|
||||
options.addOption(bool, "tracy_no_verify", tracy_no_verify);
|
||||
options.addOption(bool, "tracy_no_vsync_capture", tracy_no_vsync_capture);
|
||||
options.addOption(bool, "tracy_no_frame_image", tracy_no_frame_image);
|
||||
options.addOption(bool, "tracy_no_system_tracing", tracy_no_system_tracing);
|
||||
options.addOption(bool, "tracy_delayed_init", tracy_delayed_init);
|
||||
options.addOption(bool, "tracy_manual_lifetime", tracy_manual_lifetime);
|
||||
options.addOption(bool, "tracy_fibers", tracy_fibers);
|
||||
options.addOption(bool, "tracy_no_crash_handler", tracy_no_crash_handler);
|
||||
options.addOption(bool, "tracy_timer_fallback", tracy_timer_fallback);
|
||||
options.addOption(bool, "shared", shared);
|
||||
|
||||
const dep_src = b.dependency("src", .{});
|
||||
|
||||
const tracy_module = b.addModule("tracy", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
tracy_module.addImport("tracy-options", options.createModule());
|
||||
tracy_module.addIncludePath(dep_src.path("./public"));
|
||||
|
||||
const tracy_client = b.addLibrary(.{
|
||||
.linkage = if (shared) .dynamic else .static,
|
||||
.name = "tracy",
|
||||
.root_module = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
|
||||
if (target.result.os.tag == .windows) {
|
||||
tracy_client.root_module.linkSystemLibrary("dbghelp", .{});
|
||||
tracy_client.root_module.linkSystemLibrary("ws2_32", .{});
|
||||
}
|
||||
if (target.result.abi != .msvc) {
|
||||
tracy_client.root_module.link_libcpp = true;
|
||||
} else {
|
||||
tracy_client.root_module.link_libc = true;
|
||||
}
|
||||
tracy_client.root_module.addCSourceFile(.{
|
||||
.file = dep_src.path("./public/TracyClient.cpp"),
|
||||
.flags = if (target.result.os.tag == .windows) &.{"-fms-extensions"} else &.{},
|
||||
});
|
||||
tracy_client.installHeadersDirectory(dep_src.path("public"), "", .{
|
||||
.include_extensions = &.{".h", ".hpp"},
|
||||
});
|
||||
if (tracy_enable)
|
||||
tracy_client.root_module.addCMacro("TRACY_ENABLE", "1");
|
||||
if (tracy_on_demand)
|
||||
tracy_client.root_module.addCMacro("TRACY_ON_DEMAND", "1");
|
||||
if (tracy_callstack) |depth| {
|
||||
tracy_client.root_module.addCMacro("TRACY_CALLSTACK", "\"" ++ digits2(depth) ++ "\"");
|
||||
}
|
||||
if (tracy_no_callstack)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_CALLSTACK", "1");
|
||||
if (tracy_no_callstack_inlines)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_CALLSTACK_INLINES", "1");
|
||||
if (tracy_only_localhost)
|
||||
tracy_client.root_module.addCMacro("TRACY_ONLY_LOCALHOST", "1");
|
||||
if (tracy_no_broadcast)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_BROADCAST", "1");
|
||||
if (tracy_only_ipv4)
|
||||
tracy_client.root_module.addCMacro("TRACY_ONLY_IPV4", "1");
|
||||
if (tracy_no_code_transfer)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_CODE_TRANSFER", "1");
|
||||
if (tracy_no_context_switch)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_CONTEXT_SWITCH", "1");
|
||||
if (tracy_no_exit)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_EXIT", "1");
|
||||
if (tracy_no_sampling)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_SAMPLING", "1");
|
||||
if (tracy_no_verify)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_VERIFY", "1");
|
||||
if (tracy_no_vsync_capture)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_VSYNC_CAPTURE", "1");
|
||||
if (tracy_no_frame_image)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_FRAME_IMAGE", "1");
|
||||
if (tracy_no_system_tracing)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_SYSTEM_TRACING", "1");
|
||||
if (tracy_delayed_init)
|
||||
tracy_client.root_module.addCMacro("TRACY_DELAYED_INIT", "1");
|
||||
if (tracy_manual_lifetime)
|
||||
tracy_client.root_module.addCMacro("TRACY_MANUAL_LIFETIME", "1");
|
||||
if (tracy_fibers)
|
||||
tracy_client.root_module.addCMacro("TRACY_FIBERS", "1");
|
||||
if (tracy_no_crash_handler)
|
||||
tracy_client.root_module.addCMacro("TRACY_NO_CRASH_HANDLER", "1");
|
||||
if (tracy_timer_fallback)
|
||||
tracy_client.root_module.addCMacro("TRACY_TIMER_FALLBACK", "1");
|
||||
if (shared and target.result.os.tag == .windows)
|
||||
tracy_client.root_module.addCMacro("TRACY_EXPORTS", "1");
|
||||
b.installArtifact(tracy_client);
|
||||
}
|
||||
13
libs/tracy/build.zig.zon
Normal file
13
libs/tracy/build.zig.zon
Normal file
@ -0,0 +1,13 @@
|
||||
.{
|
||||
.name = .tracy,
|
||||
.version = "0.0.1",
|
||||
.dependencies = .{
|
||||
.src = .{
|
||||
.url = "https://github.com/wolfpld/tracy/archive/refs/tags/v0.13.1.tar.gz",
|
||||
.hash = "N-V-__8AAOncKwEm1F9c5LrT7HMNmRMYX8-fAoqpc6YyTu9X",
|
||||
},
|
||||
},
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.paths = .{""},
|
||||
.fingerprint = 0x255a89eeaaacb778,
|
||||
}
|
||||
376
libs/tracy/src/root.zig
Normal file
376
libs/tracy/src/root.zig
Normal file
@ -0,0 +1,376 @@
|
||||
const std = @import("std");
|
||||
const digits2 = std.fmt.digits2;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const options = @import("tracy-options");
|
||||
|
||||
const c = @cImport({
|
||||
if (options.tracy_enable) @cDefine("TRACY_ENABLE", {});
|
||||
if (options.tracy_on_demand) @cDefine("TRACY_ON_DEMAND", {});
|
||||
if (options.tracy_callstack) |depth| @cDefine("TRACY_CALLSTACK", "\"" ++ digits2(depth) ++ "\"");
|
||||
if (options.tracy_no_callstack) @cDefine("TRACY_NO_CALLSTACK", {});
|
||||
if (options.tracy_no_callstack_inlines) @cDefine("TRACY_NO_CALLSTACK_INLINES", {});
|
||||
if (options.tracy_only_localhost) @cDefine("TRACY_ONLY_LOCALHOST", {});
|
||||
if (options.tracy_no_broadcast) @cDefine("TRACY_NO_BROADCAST", {});
|
||||
if (options.tracy_only_ipv4) @cDefine("TRACY_ONLY_IPV4", {});
|
||||
if (options.tracy_no_code_transfer) @cDefine("TRACY_NO_CODE_TRANSFER", {});
|
||||
if (options.tracy_no_context_switch) @cDefine("TRACY_NO_CONTEXT_SWITCH", {});
|
||||
if (options.tracy_no_exit) @cDefine("TRACY_NO_EXIT", {});
|
||||
if (options.tracy_no_sampling) @cDefine("TRACY_NO_SAMPLING", {});
|
||||
if (options.tracy_no_verify) @cDefine("TRACY_NO_VERIFY", {});
|
||||
if (options.tracy_no_vsync_capture) @cDefine("TRACY_NO_VSYNC_CAPTURE", {});
|
||||
if (options.tracy_no_frame_image) @cDefine("TRACY_NO_FRAME_IMAGE", {});
|
||||
if (options.tracy_no_system_tracing) @cDefine("TRACY_NO_SYSTEM_TRACING", {});
|
||||
if (options.tracy_delayed_init) @cDefine("TRACY_DELAYED_INIT", {});
|
||||
if (options.tracy_manual_lifetime) @cDefine("TRACY_MANUAL_LIFETIME", {});
|
||||
if (options.tracy_fibers) @cDefine("TRACY_FIBERS", {});
|
||||
if (options.tracy_no_crash_handler) @cDefine("TRACY_NO_CRASH_HANDLER", {});
|
||||
if (options.tracy_timer_fallback) @cDefine("TRACY_TIMER_FALLBACK", {});
|
||||
if (options.shared and builtin.os.tag == .windows) @cDefine("TRACY_IMPORTS", {});
|
||||
|
||||
@cInclude("tracy/TracyC.h");
|
||||
});
|
||||
|
||||
pub inline fn setThreadName(comptime name: [:0]const u8) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_set_thread_name(name);
|
||||
}
|
||||
|
||||
pub inline fn startupProfiler() void {
|
||||
if (!options.tracy_enable) return;
|
||||
if (!options.tracy_manual_lifetime) return;
|
||||
c.___tracy_startup_profiler();
|
||||
}
|
||||
|
||||
pub inline fn shutdownProfiler() void {
|
||||
if (!options.tracy_enable) return;
|
||||
if (!options.tracy_manual_lifetime) return;
|
||||
c.___tracy_shutdown_profiler();
|
||||
}
|
||||
|
||||
pub inline fn profilerStarted() bool {
|
||||
if (!options.tracy_enable) return false;
|
||||
if (!options.tracy_manual_lifetime) return true;
|
||||
return c.___tracy_profiler_started() != 0;
|
||||
}
|
||||
|
||||
pub inline fn isConnected() bool {
|
||||
if (!options.tracy_enable) return false;
|
||||
return c.___tracy_connected() > 0;
|
||||
}
|
||||
|
||||
pub inline fn frameMark() void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_frame_mark(null);
|
||||
}
|
||||
|
||||
pub inline fn frameMarkNamed(comptime name: [:0]const u8) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_frame_mark(name);
|
||||
}
|
||||
|
||||
const DiscontinuousFrame = struct {
|
||||
name: [:0]const u8,
|
||||
|
||||
pub inline fn deinit(frame: *const DiscontinuousFrame) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_frame_mark_end(frame.name);
|
||||
}
|
||||
};
|
||||
|
||||
pub inline fn initDiscontinuousFrame(comptime name: [:0]const u8) DiscontinuousFrame {
|
||||
if (!options.tracy_enable) return .{ .name = name };
|
||||
c.___tracy_emit_frame_mark_start(name);
|
||||
return .{ .name = name };
|
||||
}
|
||||
|
||||
pub inline fn frameImage(image: *anyopaque, width: u16, height: u16, offset: u8, flip: bool) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_frame_mark_image(image, width, height, offset, @as(c_int, @intFromBool(flip)));
|
||||
}
|
||||
|
||||
pub const ZoneOptions = struct {
|
||||
active: bool = true,
|
||||
name: ?[]const u8 = null,
|
||||
color: ?u32 = null,
|
||||
};
|
||||
|
||||
const ZoneContext = if (options.tracy_enable) extern struct {
|
||||
ctx: c.___tracy_c_zone_context,
|
||||
|
||||
pub inline fn deinit(zone: *const ZoneContext) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_zone_end(zone.ctx);
|
||||
}
|
||||
|
||||
pub inline fn name(zone: *const ZoneContext, zone_name: []const u8) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_zone_name(zone.ctx, zone_name.ptr, zone_name.len);
|
||||
}
|
||||
|
||||
pub inline fn text(zone: *const ZoneContext, zone_text: []const u8) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_zone_text(zone.ctx, zone_text.ptr, zone_text.len);
|
||||
}
|
||||
|
||||
pub inline fn color(zone: *const ZoneContext, zone_color: u32) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_zone_color(zone.ctx, zone_color);
|
||||
}
|
||||
|
||||
pub inline fn value(zone: *const ZoneContext, zone_value: u64) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_zone_value(zone.ctx, zone_value);
|
||||
}
|
||||
} else struct {
|
||||
pub inline fn deinit(_: *const ZoneContext) void {}
|
||||
pub inline fn name(_: *const ZoneContext, _: []const u8) void {}
|
||||
pub inline fn text(_: *const ZoneContext, _: []const u8) void {}
|
||||
pub inline fn color(_: *const ZoneContext, _: u32) void {}
|
||||
pub inline fn value(_: *const ZoneContext, _: u64) void {}
|
||||
};
|
||||
|
||||
pub inline fn initZone(comptime src: std.builtin.SourceLocation, comptime opts: ZoneOptions) ZoneContext {
|
||||
if (!options.tracy_enable) return .{};
|
||||
const active: c_int = @intFromBool(opts.active);
|
||||
|
||||
const static = struct {
|
||||
var src_loc = c.___tracy_source_location_data{
|
||||
.name = if (opts.name) |name| name.ptr else null,
|
||||
.function = src.fn_name.ptr,
|
||||
.file = src.file,
|
||||
.line = 0,
|
||||
.color = opts.color orelse 0,
|
||||
};
|
||||
};
|
||||
|
||||
// src.line magically is not comptime https://github.com/ziglang/zig/pull/12016#issuecomment-1178092847
|
||||
static.src_loc.line = src.line;
|
||||
|
||||
if (!options.tracy_no_callstack) {
|
||||
if (options.tracy_callstack) |depth| {
|
||||
return .{
|
||||
.ctx = c.___tracy_emit_zone_begin_callstack(&static.src_loc, depth, active),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.ctx = c.___tracy_emit_zone_begin(&static.src_loc, active),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn plot(comptime T: type, comptime name: [:0]const u8, value: T) void {
|
||||
if (!options.tracy_enable) return;
|
||||
|
||||
const type_info = @typeInfo(T);
|
||||
switch (type_info) {
|
||||
.int => |int_type| {
|
||||
if (int_type.bits > 64) @compileError("Too large int to plot");
|
||||
if (int_type.signedness == .unsigned and int_type.bits > 63) @compileError("Too large unsigned int to plot");
|
||||
c.___tracy_emit_plot_int(name, value);
|
||||
},
|
||||
.float => |float_type| {
|
||||
if (float_type.bits <= 32) {
|
||||
c.___tracy_emit_plot_float(name, value);
|
||||
} else if (float_type.bits <= 64) {
|
||||
c.___tracy_emit_plot(name, value);
|
||||
} else {
|
||||
@compileError("Too large float to plot");
|
||||
}
|
||||
},
|
||||
else => @compileError("Unsupported plot value type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub const PlotType = enum(c_int) {
|
||||
Number,
|
||||
Memory,
|
||||
Percentage,
|
||||
Watt,
|
||||
};
|
||||
|
||||
pub const PlotConfig = struct {
|
||||
plot_type: PlotType,
|
||||
step: c_int,
|
||||
fill: c_int,
|
||||
color: u32,
|
||||
};
|
||||
|
||||
pub inline fn plotConfig(comptime name: [:0]const u8, comptime config: PlotConfig) void {
|
||||
if (!options.tracy_enable) return;
|
||||
c.___tracy_emit_plot_config(
|
||||
name,
|
||||
@intFromEnum(config.plot_type),
|
||||
config.step,
|
||||
config.fill,
|
||||
config.color,
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn message(comptime msg: [:0]const u8) void {
|
||||
if (!options.tracy_enable) return;
|
||||
const depth = options.tracy_callstack orelse 0;
|
||||
c.___tracy_emit_messageL(msg, depth);
|
||||
}
|
||||
|
||||
pub inline fn messageColor(comptime msg: [:0]const u8, color: u32) void {
|
||||
if (!options.tracy_enable) return;
|
||||
const depth = options.tracy_callstack orelse 0;
|
||||
c.___tracy_emit_messageLC(msg, color, depth);
|
||||
}
|
||||
|
||||
const tracy_message_buffer_size = if (options.tracy_enable) 4096 else 0;
|
||||
threadlocal var tracy_message_buffer: [tracy_message_buffer_size]u8 = undefined;
|
||||
|
||||
pub inline fn print(comptime fmt: []const u8, args: anytype) void {
|
||||
if (!options.tracy_enable) return;
|
||||
const depth = options.tracy_callstack orelse 0;
|
||||
|
||||
var stream = std.io.fixedBufferStream(&tracy_message_buffer);
|
||||
stream.writer().print(fmt, args) catch {};
|
||||
|
||||
const written = stream.getWritten();
|
||||
c.___tracy_emit_message(written.ptr, written.len, depth);
|
||||
}
|
||||
|
||||
pub inline fn printColor(comptime fmt: []const u8, args: anytype, color: u32) void {
|
||||
if (!options.tracy_enable) return;
|
||||
const depth = options.tracy_callstack orelse 0;
|
||||
|
||||
var stream = std.io.fixedBufferStream(&tracy_message_buffer);
|
||||
stream.writer().print(fmt, args) catch {};
|
||||
|
||||
const written = stream.getWritten();
|
||||
c.___tracy_emit_messageC(written.ptr, written.len, color, depth);
|
||||
}
|
||||
|
||||
pub inline fn printAppInfo(comptime fmt: []const u8, args: anytype) void {
|
||||
if (!options.tracy_enable) return;
|
||||
|
||||
var stream = std.io.fixedBufferStream(&tracy_message_buffer);
|
||||
stream.reset();
|
||||
stream.writer().print(fmt, args) catch {};
|
||||
|
||||
const written = stream.getWritten();
|
||||
c.___tracy_emit_message_appinfo(written.ptr, written.len);
|
||||
}
|
||||
|
||||
pub const TracingAllocator = struct {
|
||||
parent_allocator: std.mem.Allocator,
|
||||
pool_name: ?[:0]const u8,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(parent_allocator: std.mem.Allocator) Self {
|
||||
return .{
|
||||
.parent_allocator = parent_allocator,
|
||||
.pool_name = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initNamed(comptime pool_name: [:0]const u8, parent_allocator: std.mem.Allocator) Self {
|
||||
return .{
|
||||
.parent_allocator = parent_allocator,
|
||||
.pool_name = pool_name,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn allocator(self: *Self) std.mem.Allocator {
|
||||
return .{
|
||||
.ptr = self,
|
||||
.vtable = &.{
|
||||
.alloc = alloc,
|
||||
.resize = resize,
|
||||
.remap = remap,
|
||||
.free = free,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn alloc(
|
||||
ctx: *anyopaque,
|
||||
len: usize,
|
||||
ptr_align: std.mem.Alignment,
|
||||
ret_addr: usize,
|
||||
) ?[*]u8 {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const result = self.parent_allocator.rawAlloc(len, ptr_align, ret_addr);
|
||||
if (!options.tracy_enable) return result;
|
||||
|
||||
if (self.pool_name) |name| {
|
||||
c.___tracy_emit_memory_alloc_named(result, len, 0, name.ptr);
|
||||
} else {
|
||||
c.___tracy_emit_memory_alloc(result, len, 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn resize(
|
||||
ctx: *anyopaque,
|
||||
buf: []u8,
|
||||
buf_align: std.mem.Alignment,
|
||||
new_len: usize,
|
||||
ret_addr: usize,
|
||||
) bool {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const result = self.parent_allocator.rawResize(buf, buf_align, new_len, ret_addr);
|
||||
if (!result) return false;
|
||||
|
||||
if (!options.tracy_enable) return true;
|
||||
|
||||
if (self.pool_name) |name| {
|
||||
c.___tracy_emit_memory_free_named(buf.ptr, 0, name.ptr);
|
||||
c.___tracy_emit_memory_alloc_named(buf.ptr, new_len, 0, name.ptr);
|
||||
} else {
|
||||
c.___tracy_emit_memory_free(buf.ptr, 0);
|
||||
c.___tracy_emit_memory_alloc(buf.ptr, new_len, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn remap(
|
||||
ctx: *anyopaque,
|
||||
buf: []u8,
|
||||
buf_align: std.mem.Alignment,
|
||||
new_len: usize,
|
||||
ret_addr: usize,
|
||||
) ?[*]u8 {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
const result = self.parent_allocator.rawRemap(buf, buf_align, new_len, ret_addr);
|
||||
const new_buf = result orelse return null;
|
||||
|
||||
if (!options.tracy_enable) return new_buf;
|
||||
|
||||
if (self.pool_name) |name| {
|
||||
c.___tracy_emit_memory_free_named(buf.ptr, 0, name.ptr);
|
||||
c.___tracy_emit_memory_alloc_named(new_buf, new_len, 0, name.ptr);
|
||||
} else {
|
||||
c.___tracy_emit_memory_free(buf.ptr, 0);
|
||||
c.___tracy_emit_memory_alloc(new_buf, new_len, 0);
|
||||
}
|
||||
|
||||
return new_buf;
|
||||
}
|
||||
|
||||
fn free(
|
||||
ctx: *anyopaque,
|
||||
buf: []u8,
|
||||
buf_align: std.mem.Alignment,
|
||||
ret_addr: usize,
|
||||
) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||
|
||||
if (options.tracy_enable) {
|
||||
if (self.pool_name) |name| {
|
||||
c.___tracy_emit_memory_free_named(buf.ptr, 0, name.ptr);
|
||||
} else {
|
||||
c.___tracy_emit_memory_free(buf.ptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
self.parent_allocator.rawFree(buf, buf_align, ret_addr);
|
||||
}
|
||||
};
|
||||
57
scripts/png-to-icon.zig
Normal file
57
scripts/png-to-icon.zig
Normal file
@ -0,0 +1,57 @@
|
||||
const std = @import("std");
|
||||
const STBImage = @import("stb_image");
|
||||
|
||||
// https://en.wikipedia.org/wiki/ICO_(file_format)#Icon_file_structure
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
|
||||
if (args.len != 3) {
|
||||
std.debug.print("Usage: ./png-to-icon <png-file> <output-ico>", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const cwd = std.fs.cwd();
|
||||
const input_png_path = args[1];
|
||||
const output_ico_path = args[2];
|
||||
|
||||
const input_png_data = try cwd.readFileAlloc(allocator, input_png_path, 1024 * 1024 * 5);
|
||||
defer allocator.free(input_png_data);
|
||||
|
||||
const png_image = try STBImage.load(input_png_data);
|
||||
defer png_image.deinit();
|
||||
|
||||
std.debug.assert(png_image.width > 0 and png_image.width <= 256);
|
||||
std.debug.assert(png_image.height > 0 and png_image.height <= 256);
|
||||
|
||||
const output_ico_file = try std.fs.cwd().createFile(output_ico_path, .{ });
|
||||
defer output_ico_file.close();
|
||||
|
||||
var buffer: [4096 * 4]u8 = undefined;
|
||||
var writer = output_ico_file.writer(&buffer);
|
||||
|
||||
// ICONDIR structure
|
||||
try writer.interface.writeInt(u16, 0, .little); // Must always be zero
|
||||
try writer.interface.writeInt(u16, 1, .little); // Image type. 1 for .ICO
|
||||
try writer.interface.writeInt(u16, 1, .little); // Number of images
|
||||
|
||||
// ICONDIRENTRY structure
|
||||
try writer.interface.writeInt(u8, @truncate(png_image.width), .little); // Image width
|
||||
try writer.interface.writeInt(u8, @truncate(png_image.height), .little); // Image height
|
||||
try writer.interface.writeInt(u8, 0, .little); // Number of colors in color pallete. 0 means that color pallete is not used
|
||||
try writer.interface.writeInt(u8, 0, .little); // Must always be zero
|
||||
try writer.interface.writeInt(u16, 0, .little); // Color plane
|
||||
try writer.interface.writeInt(u16, 32, .little); // Bits per pixel
|
||||
try writer.interface.writeInt(u32, @intCast(input_png_data.len), .little); // Image size in bytes
|
||||
try writer.interface.writeInt(u32, 22, .little); // Offset to image data from the start
|
||||
|
||||
// PNG image data
|
||||
try writer.interface.writeAll(input_png_data);
|
||||
try writer.interface.flush();
|
||||
}
|
||||
|
||||
42
src/app.zig
Normal file
42
src/app.zig
Normal file
@ -0,0 +1,42 @@
|
||||
const CLI = @import("./cli.zig");
|
||||
|
||||
const App = @This();
|
||||
|
||||
pub fn init(self: *App) !?u8 {
|
||||
_ = self; // autofix
|
||||
// var cli: CLI = undefined;
|
||||
// cli.init(plt.io, plt.gpa);
|
||||
// defer cli.deinit();
|
||||
//
|
||||
// const opt_plugin_path = try cli.addOption(.{
|
||||
// .kind = .single_argument,
|
||||
// .long_name = "plugin-path"
|
||||
// });
|
||||
//
|
||||
// var result = cli.parse(plt.gpa, opts.cli_args) catch {
|
||||
// return 1;
|
||||
// };
|
||||
// defer result.deinit();
|
||||
//
|
||||
// var cwd = Io.Dir.cwd();
|
||||
// const root_dir = try cwd.realPathFileAlloc(plt.io, ".", plt.gpa);
|
||||
// defer plt.gpa.free(root_dir);
|
||||
//
|
||||
// var plugin_path: ?[]const u8 = null;
|
||||
// if (result.getOptionArgument(opt_plugin_path)) |absolute_plugin_path| {
|
||||
// plugin_path = FileWatcher.toRelativePath(root_dir, absolute_plugin_path);
|
||||
// if (plugin_path == null) {
|
||||
// return error.PluginPathOutsideOfProject;
|
||||
// }
|
||||
//
|
||||
// log.debug("Loaded dynamic plugin from: '{s}'", .{plugin_path.?});
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn frame(self: *App) !void {
|
||||
_ = self; // autofix
|
||||
}
|
||||
|
||||
pub fn deinit(self: *App) void {
|
||||
_ = self; // autofix
|
||||
}
|
||||
17
src/cli.zig
17
src/cli.zig
@ -244,14 +244,11 @@ const Parser = struct {
|
||||
}
|
||||
};
|
||||
|
||||
fn printErrorAndExit(self: *CLI, comptime fmt: []const u8, args: anytype) noreturn {
|
||||
fn printError(self: *CLI, comptime fmt: []const u8, args: anytype) void {
|
||||
self.stderr.write("ERROR: " ++ fmt ++ "\n", args);
|
||||
self.stderr.flush();
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
// TODO: I don't like the fact that this call `std.process.exit()`
|
||||
// But for now this is the most convenient thing.
|
||||
pub fn parse(self: *CLI, gpa: std.mem.Allocator, args: []const []const u8) !ParseResult {
|
||||
var result = try ParseResult.init(gpa, self.options.items.len);
|
||||
errdefer result.deinit();
|
||||
@ -270,7 +267,8 @@ pub fn parse(self: *CLI, gpa: std.mem.Allocator, args: []const []const u8) !Pars
|
||||
|
||||
if (option.kind == .single_argument) {
|
||||
const argument = parser.take() orelse {
|
||||
self.printErrorAndExit("Missing required argument for '{s}'", .{arg});
|
||||
self.printError("Missing required argument for '{s}'", .{arg});
|
||||
return error.MissingArgument;
|
||||
};
|
||||
|
||||
result_value.argument = argument;
|
||||
@ -280,7 +278,8 @@ pub fn parse(self: *CLI, gpa: std.mem.Allocator, args: []const []const u8) !Pars
|
||||
continue :next_arg;
|
||||
}
|
||||
|
||||
self.printErrorAndExit("Unknown option '{s}'", .{arg});
|
||||
self.printError("Unknown option '{s}'", .{arg});
|
||||
return error.UnknownOption;
|
||||
}
|
||||
|
||||
if (result.command == null) {
|
||||
@ -291,9 +290,11 @@ pub fn parse(self: *CLI, gpa: std.mem.Allocator, args: []const []const u8) !Pars
|
||||
}
|
||||
}
|
||||
|
||||
self.printErrorAndExit("Unknown command '{s}'", .{arg});
|
||||
self.printError("Unknown command '{s}'", .{arg});
|
||||
return error.UnknownCommand;
|
||||
} else {
|
||||
self.printErrorAndExit("Unknown argument '{s}'", .{arg});
|
||||
self.printError("Unknown argument '{s}'", .{arg});
|
||||
return error.UnknownArgument;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
src/color.zig
Normal file
39
src/color.zig
Normal file
@ -0,0 +1,39 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Color = @This();
|
||||
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
a: f32,
|
||||
|
||||
pub const black = rgb(0, 0, 0);
|
||||
pub const white = rgb(255, 255, 255);
|
||||
|
||||
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Color {
|
||||
assert(0 <= a and a <= 1);
|
||||
return .{
|
||||
.r = @as(f32, @floatFromInt(r)) / 255,
|
||||
.g = @as(f32, @floatFromInt(g)) / 255,
|
||||
.b = @as(f32, @floatFromInt(b)) / 255,
|
||||
.a = a,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn rgb(r: u8, g: u8, b: u8) Color {
|
||||
return rgba(r, g, b, 1);
|
||||
}
|
||||
|
||||
pub fn rgb_hex(text: []const u8) ?Color {
|
||||
if (text.len != 7) {
|
||||
return null;
|
||||
}
|
||||
if (text[0] != '#') {
|
||||
return null;
|
||||
}
|
||||
const r = std.fmt.parseInt(u8, text[1..3], 16) catch return null;
|
||||
const g = std.fmt.parseInt(u8, text[3..5], 16) catch return null;
|
||||
const b = std.fmt.parseInt(u8, text[5..7], 16) catch return null;
|
||||
return rgb(r, g, b);
|
||||
}
|
||||
@ -49,6 +49,23 @@ pub fn init(gpa: Allocator, root_dir: []const u8) !FileWatcher {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *FileWatcher, gpa: Allocator) void {
|
||||
gpa.free(self.root_dir);
|
||||
|
||||
gpa.free(self.inotify.buf);
|
||||
self.inotify.deinit();
|
||||
|
||||
for (self.files.items) |file| {
|
||||
file.deinit(gpa);
|
||||
}
|
||||
self.files.deinit(gpa);
|
||||
|
||||
for (self.watches.items) |watch| {
|
||||
watch.deinit(gpa);
|
||||
}
|
||||
self.watches.deinit(gpa);
|
||||
}
|
||||
|
||||
fn findFileIndex(self: *FileWatcher, path: []const u8) ?usize {
|
||||
for (0.., self.files.items) |i, file| {
|
||||
if (std.mem.eql(u8, file.path, path)) {
|
||||
@ -111,14 +128,22 @@ fn attemptRegisteringWatch(self: *FileWatcher, watch: *Watch) !void {
|
||||
};
|
||||
}
|
||||
|
||||
fn isInside(outer_path: []const u8, inner_path: []const u8) bool {
|
||||
pub fn toRelativePath(outer_path: []const u8, inner_path: []const u8) ?[]const u8 {
|
||||
if (inner_path.len == outer_path.len) {
|
||||
return std.mem.eql(u8, inner_path, outer_path);
|
||||
} else if (inner_path.len > outer_path.len) {
|
||||
return std.mem.startsWith(u8, inner_path, outer_path) and inner_path[outer_path.len] == std.fs.path.sep;
|
||||
} else {
|
||||
return false;
|
||||
if (std.mem.eql(u8, inner_path, outer_path)) {
|
||||
return "";
|
||||
}
|
||||
} else if (inner_path.len > outer_path.len) {
|
||||
if (std.mem.startsWith(u8, inner_path, outer_path) and inner_path[outer_path.len] == std.fs.path.sep) {
|
||||
return inner_path[(outer_path.len+1)..];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn isInside(outer_path: []const u8, inner_path: []const u8) bool {
|
||||
return toRelativePath(outer_path, inner_path) != null;
|
||||
}
|
||||
|
||||
fn unregisterWatchesRecursively(self: *FileWatcher, dir: []const u8) !void {
|
||||
@ -293,20 +318,3 @@ pub fn next(self: *FileWatcher, io: Io) !?[]const u8 {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *FileWatcher, gpa: Allocator) void {
|
||||
gpa.free(self.root_dir);
|
||||
|
||||
gpa.free(self.inotify.buf);
|
||||
self.inotify.deinit();
|
||||
|
||||
for (self.files.items) |file| {
|
||||
file.deinit(gpa);
|
||||
}
|
||||
self.files.deinit(gpa);
|
||||
|
||||
for (self.watches.items) |watch| {
|
||||
watch.deinit(gpa);
|
||||
}
|
||||
self.watches.deinit(gpa);
|
||||
}
|
||||
|
||||
93
src/main.zig
93
src/main.zig
@ -1,93 +1,16 @@
|
||||
const std = @import("std");
|
||||
const CLI = @import("cli.zig");
|
||||
const build_options = @import("build_options");
|
||||
const builtin = @import("builtin");
|
||||
const FileWatcher = @import("file_watcher.zig");
|
||||
const Io = std.Io;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// TODO: zig build --watch --color off --summary none
|
||||
const Platform = @import("platform.zig");
|
||||
const App = @import("app.zig");
|
||||
|
||||
const Plugin = @import("plugin_api").Plugin;
|
||||
pub fn main(init: std.process.Init) void {
|
||||
Platform.run(App, .{
|
||||
.init = init,
|
||||
|
||||
pub fn getStaticPlugin() !Plugin {
|
||||
if (!build_options.plugin_static) {
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
return @import("plugin").api;
|
||||
}
|
||||
|
||||
const DynamicPlugin = struct {
|
||||
lib: std.DynLib,
|
||||
plugin: Plugin,
|
||||
|
||||
pub fn init(path: []const u8) !DynamicPlugin {
|
||||
var lib = try std.DynLib.open(path);
|
||||
errdefer lib.close();
|
||||
|
||||
var plugin: Plugin = undefined;
|
||||
inline for (@typeInfo(Plugin).@"struct".fields) |field| {
|
||||
@field(plugin, field.name) = lib.lookup(field.type, field.name) orelse return error.SymbolNotFound;
|
||||
}
|
||||
|
||||
return DynamicPlugin{
|
||||
.lib = lib,
|
||||
.plugin = plugin
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DynamicPlugin) void {
|
||||
self.lib.close();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
// This is appropriate for anything that lives as long as the process.
|
||||
const arena: std.mem.Allocator = init.arena.allocator();
|
||||
const gpa = init.gpa;
|
||||
|
||||
const io = init.io;
|
||||
|
||||
var cli: CLI = undefined;
|
||||
cli.init(io, init.gpa);
|
||||
defer cli.deinit();
|
||||
|
||||
const opt_plugin_path = try cli.addOption(.{
|
||||
.kind = .single_argument,
|
||||
.long_name = "plugin-path"
|
||||
.window_title = "Gaem",
|
||||
.width = 800,
|
||||
.height = 600,
|
||||
});
|
||||
|
||||
const args = try init.minimal.args.toSlice(arena);
|
||||
var result = try cli.parse(gpa, args);
|
||||
defer result.deinit();
|
||||
|
||||
const plugin_path: ?[]const u8 = result.getOptionArgument(opt_plugin_path);
|
||||
|
||||
if (plugin_path != null) {
|
||||
var foo = try DynamicPlugin.init(plugin_path.?);
|
||||
defer foo.deinit();
|
||||
}
|
||||
|
||||
var cwd = Io.Dir.cwd();
|
||||
|
||||
const root_dir = try cwd.realPathFileAlloc(io, ".", gpa);
|
||||
defer gpa.free(root_dir);
|
||||
|
||||
var file_watcher = try FileWatcher.init(gpa, root_dir);
|
||||
defer file_watcher.deinit(gpa);
|
||||
|
||||
try file_watcher.add(gpa, "test.txt", .fromMilliseconds(150));
|
||||
try file_watcher.add(gpa, "foo/test.txt", .fromMilliseconds(150));
|
||||
|
||||
while (true) {
|
||||
while (try file_watcher.next(io)) |path| {
|
||||
std.debug.print("path: {s}\n", .{path});
|
||||
}
|
||||
|
||||
try io.sleep(.fromMilliseconds(100), .real);
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
447
src/math.zig
Normal file
447
src/math.zig
Normal file
@ -0,0 +1,447 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const bytes_per_kib = 1024;
|
||||
pub const bytes_per_mib = bytes_per_kib * 1024;
|
||||
pub const bytes_per_gib = bytes_per_mib * 1024;
|
||||
|
||||
pub const bytes_per_kb = 1000;
|
||||
pub const bytes_per_mb = bytes_per_kb * 1000;
|
||||
pub const bytes_per_gb = bytes_per_mb * 1000;
|
||||
|
||||
pub const Range = struct {
|
||||
from: f32,
|
||||
to: f32,
|
||||
|
||||
pub const zero = init(0, 0);
|
||||
|
||||
pub fn init(from: f32, to: f32) Range {
|
||||
return Range{
|
||||
.from = from,
|
||||
.to = to
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getSize(self: Range) f32 {
|
||||
return @abs(self.from - self.to);
|
||||
}
|
||||
|
||||
pub fn random(self: Range, rng: std.Random) f32 {
|
||||
return self.from + rng.float(f32) * (self.to - self.from);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec2 = extern struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
|
||||
pub const zero = init(0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32) Vec2 {
|
||||
return Vec2{
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initFromInt(T: type, x: T, y: T) Vec2 {
|
||||
return .init(@floatFromInt(x), @floatFromInt(y));
|
||||
}
|
||||
|
||||
pub fn initAngle(angle: f32) Vec2 {
|
||||
return Vec2{
|
||||
.x = @cos(angle),
|
||||
.y = @sin(angle),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn rotateLeft90(self: Vec2) Vec2 {
|
||||
return Vec2.init(self.y, -self.x);
|
||||
}
|
||||
|
||||
pub fn rotateRight90(self: Vec2) Vec2 {
|
||||
return Vec2.init(-self.y, self.x);
|
||||
}
|
||||
|
||||
pub fn rotate(self: Vec2, angle: f32) Vec2 {
|
||||
return init(
|
||||
@cos(angle) * self.x - @sin(angle) * self.y,
|
||||
@sin(angle) * self.x + @cos(angle) * self.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn rotateAround(self: Vec2, angle: f32, origin: Vec2) Vec2 {
|
||||
return self.sub(origin).rotate(angle).add(origin);
|
||||
}
|
||||
|
||||
pub fn getAngle(self: Vec2) f32 {
|
||||
return std.math.atan2(self.y, self.x);
|
||||
}
|
||||
|
||||
pub fn flip(self: Vec2) Vec2 {
|
||||
return Vec2.init(-self.x, -self.y);
|
||||
}
|
||||
|
||||
pub fn add(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x + other.x,
|
||||
self.y + other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn sub(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x - other.x,
|
||||
self.y - other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn multiplyScalar(self: Vec2, value: f32) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x * value,
|
||||
self.y * value,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn multiply(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x * other.x,
|
||||
self.y * other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn divide(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x / other.x,
|
||||
self.y / other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn divideScalar(self: Vec2, value: f32) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x / value,
|
||||
self.y / value,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn lengthSqr(self: Vec2) f32 {
|
||||
return self.x*self.x + self.y*self.y;
|
||||
}
|
||||
|
||||
pub fn length(self: Vec2) f32 {
|
||||
return @sqrt(self.lengthSqr());
|
||||
}
|
||||
|
||||
pub fn distance(self: Vec2, other: Vec2) f32 {
|
||||
return self.sub(other).length();
|
||||
}
|
||||
|
||||
pub fn distanceSqr(self: Vec2, other: Vec2) f32 {
|
||||
return self.sub(other).lengthSqr();
|
||||
}
|
||||
|
||||
pub fn limitLength(self: Vec2, max_length: f32) Vec2 {
|
||||
const self_length = self.length();
|
||||
if (self_length > max_length) {
|
||||
if (self_length == 0) {
|
||||
return Vec2.init(0, 0);
|
||||
}
|
||||
return self.divideScalar(self_length / max_length);
|
||||
} else {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalized(self: Vec2) Vec2 {
|
||||
const self_length = self.length();
|
||||
if (self_length == 0) {
|
||||
return Vec2.init(0, 0);
|
||||
}
|
||||
return self.divideScalar(self_length);
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec2 {
|
||||
return Vec2.init(value, value);
|
||||
}
|
||||
|
||||
pub fn eql(self: Vec2, other: Vec2) bool {
|
||||
return self.x == other.x and self.y == other.y;
|
||||
}
|
||||
|
||||
pub fn format(self: Vec2, writer: *std.io.Writer) std.io.Writer.Error!void {
|
||||
try writer.print("Vec2{{ {d}, {d} }}", .{ self.x, self.y });
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec3 = extern struct {
|
||||
x: f32, y: f32, z: f32,
|
||||
|
||||
pub const zero = init(0, 0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32, z: f32) Vec3 {
|
||||
return Vec3{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = z,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec3 {
|
||||
return Vec3.init(value, value, value);
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Vec3) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(self))));
|
||||
return ptr[0..3];
|
||||
}
|
||||
|
||||
pub fn lerp(a: Vec3, b: Vec3, t: f32) Vec3 {
|
||||
return Vec3.init(
|
||||
std.math.lerp(a.x, b.x, t),
|
||||
std.math.lerp(a.y, b.y, t),
|
||||
std.math.lerp(a.z, b.z, t),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clamp(self: Vec3, min_value: f32, max_value: f32) Vec3 {
|
||||
return Vec3.init(
|
||||
std.math.clamp(self.x, min_value, max_value),
|
||||
std.math.clamp(self.y, min_value, max_value),
|
||||
std.math.clamp(self.z, min_value, max_value),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec4 = extern struct {
|
||||
x: f32, y: f32, z: f32, w: f32,
|
||||
|
||||
pub const zero = init(0, 0, 0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32, z: f32, w: f32) Vec4 {
|
||||
return Vec4{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = z,
|
||||
.w = w
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initVec3XYZ(vec3: Vec3, w: f32) Vec4 {
|
||||
return init(vec3.x, vec3.y, vec3.z, w);
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec4 {
|
||||
return Vec4.init(value, value, value, value);
|
||||
}
|
||||
|
||||
pub fn multiplyMat4(left: Vec4, right: Mat4) Vec4 {
|
||||
var result: Vec4 = undefined;
|
||||
|
||||
// TODO: SIMD
|
||||
|
||||
result.x = left.x * right.columns[0][0];
|
||||
result.y = left.x * right.columns[0][1];
|
||||
result.z = left.x * right.columns[0][2];
|
||||
result.w = left.x * right.columns[0][3];
|
||||
|
||||
result.x += left.y * right.columns[1][0];
|
||||
result.y += left.y * right.columns[1][1];
|
||||
result.z += left.y * right.columns[1][2];
|
||||
result.w += left.y * right.columns[1][3];
|
||||
|
||||
result.x += left.z * right.columns[2][0];
|
||||
result.y += left.z * right.columns[2][1];
|
||||
result.z += left.z * right.columns[2][2];
|
||||
result.w += left.z * right.columns[2][3];
|
||||
|
||||
result.x += left.w * right.columns[3][0];
|
||||
result.y += left.w * right.columns[3][1];
|
||||
result.z += left.w * right.columns[3][2];
|
||||
result.w += left.w * right.columns[3][3];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn multiply(left: Vec4, right: Vec4) Vec4 {
|
||||
return init(
|
||||
left.x * right.x,
|
||||
left.y * right.y,
|
||||
left.z * right.z,
|
||||
left.w * right.w
|
||||
);
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Vec4) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(self))));
|
||||
return ptr[0..4];
|
||||
}
|
||||
|
||||
pub fn initArray(array: []const f32) Vec4 {
|
||||
return Vec4.init(array[0], array[1], array[2], array[3]);
|
||||
}
|
||||
|
||||
pub fn lerp(a: Vec4, b: Vec4, t: f32) Vec4 {
|
||||
return Vec4.init(
|
||||
std.math.lerp(a.x, b.x, t),
|
||||
std.math.lerp(a.y, b.y, t),
|
||||
std.math.lerp(a.z, b.z, t),
|
||||
std.math.lerp(a.w, b.w, t),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clamp(self: Vec4, min_value: f32, max_value: f32) Vec4 {
|
||||
return Vec4.init(
|
||||
std.math.clamp(self.x, min_value, max_value),
|
||||
std.math.clamp(self.y, min_value, max_value),
|
||||
std.math.clamp(self.z, min_value, max_value),
|
||||
std.math.clamp(self.w, min_value, max_value),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn toVec3XYZ(self: Vec4) Vec3 {
|
||||
return Vec3.init(self.x, self.y, self.z);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Mat4 = extern struct {
|
||||
columns: [4][4]f32,
|
||||
|
||||
pub fn initZero() Mat4 {
|
||||
var self: Mat4 = undefined;
|
||||
@memset(self.asArray(), 0);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initIdentity() Mat4 {
|
||||
return Mat4.initDiagonal(1);
|
||||
}
|
||||
|
||||
pub fn initDiagonal(value: f32) Mat4 {
|
||||
var self = Mat4.initZero();
|
||||
self.columns[0][0] = value;
|
||||
self.columns[1][1] = value;
|
||||
self.columns[2][2] = value;
|
||||
self.columns[3][3] = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn multiply(left: Mat4, right: Mat4) Mat4 {
|
||||
var self: Mat4 = undefined;
|
||||
|
||||
inline for (.{ 0, 1, 2, 3 }) |i| {
|
||||
var column = Vec4.initArray(&right.columns[i]).multiplyMat4(left);
|
||||
@memcpy(&self.columns[i], column.asArray());
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initScale(scale: Vec3) Mat4 {
|
||||
var self = Mat4.initIdentity();
|
||||
self.columns[0][0] = scale.x;
|
||||
self.columns[1][1] = scale.y;
|
||||
self.columns[2][2] = scale.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initTranslate(offset: Vec3) Mat4 {
|
||||
var self = Mat4.initIdentity();
|
||||
self.columns[3][0] = offset.x;
|
||||
self.columns[3][1] = offset.y;
|
||||
self.columns[3][2] = offset.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Mat4) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(&self.columns))));
|
||||
return ptr[0..16];
|
||||
}
|
||||
};
|
||||
|
||||
pub const Rect = struct {
|
||||
pos: Vec2,
|
||||
size: Vec2,
|
||||
|
||||
pub const zero = Rect{
|
||||
.pos = Vec2.zero,
|
||||
.size = Vec2.zero
|
||||
};
|
||||
|
||||
pub const unit = Rect{
|
||||
.pos = Vec2.zero,
|
||||
.size = Vec2.init(1, 1)
|
||||
};
|
||||
|
||||
pub fn init(x: f32, y: f32, width: f32, height: f32) Rect {
|
||||
return Rect{
|
||||
.pos = Vec2.init(x, y),
|
||||
.size = Vec2.init(width, height)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clip(self: Rect, other: Rect) Rect {
|
||||
const left_edge = @max(self.left(), other.left());
|
||||
const right_edge = @min(self.right(), other.right());
|
||||
const top_edge = @max(self.top(), other.top());
|
||||
const bottom_edge = @min(self.bottom(), other.bottom());
|
||||
return Rect.init(
|
||||
left_edge,
|
||||
top_edge,
|
||||
right_edge - left_edge,
|
||||
bottom_edge - top_edge
|
||||
);
|
||||
}
|
||||
|
||||
pub fn left(self: Rect) f32 {
|
||||
return self.pos.x;
|
||||
}
|
||||
|
||||
pub fn right(self: Rect) f32 {
|
||||
return self.pos.x + self.size.x;
|
||||
}
|
||||
|
||||
pub fn top(self: Rect) f32 {
|
||||
return self.pos.y;
|
||||
}
|
||||
|
||||
pub fn bottom(self: Rect) f32 {
|
||||
return self.pos.y + self.size.y;
|
||||
}
|
||||
|
||||
pub fn center(self: Rect) Vec2 {
|
||||
return self.pos.add(self.size.multiplyScalar(0.5));
|
||||
}
|
||||
|
||||
pub fn multiply(self: Rect, xy: Vec2) Rect {
|
||||
return Rect{
|
||||
.pos = self.pos.multiply(xy),
|
||||
.size = self.size.multiply(xy),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn divide(self: Rect, xy: Vec2) Rect {
|
||||
return Rect{
|
||||
.pos = self.pos.divide(xy),
|
||||
.size = self.size.divide(xy),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isInside(self: Rect, pos: Vec2) bool {
|
||||
const x_overlap = self.pos.x <= pos.x and pos.x < self.pos.x + self.size.x;
|
||||
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
||||
return x_overlap and y_overlap;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Line = struct {
|
||||
p0: Vec2,
|
||||
p1: Vec2
|
||||
};
|
||||
|
||||
pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
||||
const rect = Rect{
|
||||
.pos = rect_pos,
|
||||
.size = rect_size
|
||||
};
|
||||
return rect.isInside(pos);
|
||||
}
|
||||
286
src/platform.zig
Normal file
286
src/platform.zig
Normal file
@ -0,0 +1,286 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const log = std.log.scoped(.platform);
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
const sapp = sokol.app;
|
||||
const sg = sokol.gfx;
|
||||
const sglue = sokol.glue;
|
||||
const sgl = sokol.gl;
|
||||
const simgui = sokol.imgui;
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
const tracy = @import("tracy");
|
||||
|
||||
const ig = @import("cimgui");
|
||||
|
||||
const Color = @import("./color.zig");
|
||||
|
||||
fn PlatformType(App: type) type {
|
||||
_ = App; // autofix
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
pass_action: sg.PassAction = .{},
|
||||
cli_args: []const [:0]const u8,
|
||||
|
||||
show_first_window: bool = true,
|
||||
|
||||
fn sokolInit(user_data: ?*anyopaque) callconv(.c) void {
|
||||
const self: *Self = @ptrCast(@alignCast(user_data));
|
||||
sg.setup(.{
|
||||
.environment = sglue.environment(),
|
||||
.logger = .{ .func = sokolLogCallback },
|
||||
});
|
||||
|
||||
simgui.setup(.{
|
||||
.logger = .{ .func = sokolLogCallback },
|
||||
});
|
||||
|
||||
if (@hasDecl(ig, "ImGuiConfigFlags_DockingEnable")) {
|
||||
ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_DockingEnable;
|
||||
}
|
||||
ig.igGetIO().*.ConfigFlags |= ig.ImGuiConfigFlags_ViewportsEnable;
|
||||
|
||||
self.pass_action.colors[0] = .{
|
||||
.load_action = .CLEAR,
|
||||
.clear_value = toSokolColor(.black)
|
||||
};
|
||||
}
|
||||
|
||||
fn sokolFrame(user_data: ?*anyopaque) callconv(.c) void {
|
||||
const self: *Self = @ptrCast(@alignCast(user_data));
|
||||
|
||||
tracy.frameMark();
|
||||
|
||||
simgui.newFrame(.{
|
||||
.width = sapp.width(),
|
||||
.height = sapp.height(),
|
||||
.delta_time = sapp.frameDuration(),
|
||||
.dpi_scale = sapp.dpiScale(),
|
||||
});
|
||||
|
||||
//=== UI CODE STARTS HERE
|
||||
{
|
||||
const viewport = ig.igGetMainViewport();
|
||||
ig.igSetNextWindowPos(viewport[0].WorkPos, ig.ImGuiCond_Always);
|
||||
ig.igSetNextWindowSize(viewport[0].WorkSize, ig.ImGuiCond_Always);
|
||||
ig.igSetNextWindowViewport(viewport[0].ID);
|
||||
|
||||
const window_flags =
|
||||
ig.ImGuiWindowFlags_NoDocking |
|
||||
ig.ImGuiWindowFlags_NoTitleBar |
|
||||
ig.ImGuiWindowFlags_NoCollapse |
|
||||
ig.ImGuiWindowFlags_NoResize |
|
||||
ig.ImGuiWindowFlags_NoMove |
|
||||
ig.ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ig.ImGuiWindowFlags_NoNavFocus;
|
||||
|
||||
ig.igPushStyleVar(ig.ImGuiStyleVar_WindowRounding, 0.0);
|
||||
ig.igPushStyleVar(ig.ImGuiStyleVar_WindowBorderSize, 0.0);
|
||||
ig.igPushStyleVarImVec2(ig.ImGuiStyleVar_WindowPadding, .{ .x = 0, .y = 0 });
|
||||
_ = ig.igBegin("DockSpace", null, window_flags);
|
||||
ig.igPopStyleVarEx(3);
|
||||
|
||||
const dockspace_id = ig.igGetID("MyDockSpace");
|
||||
_ = ig.igDockSpaceEx(dockspace_id, ig.ImVec2{ .x = 0, .y = 0 }, ig.ImGuiDockNodeFlags_PassthruCentralNode, null);
|
||||
|
||||
ig.igSetNextWindowPos(.{ .x = 10, .y = 30 }, ig.ImGuiCond_Once);
|
||||
ig.igSetNextWindowSize(.{ .x = 400, .y = 100 }, ig.ImGuiCond_Once);
|
||||
if (ig.igBegin("Hello Dear ImGui!", &self.show_first_window, ig.ImGuiWindowFlags_None)) {
|
||||
_ = ig.igColorEdit3("Background", &self.pass_action.colors[0].clear_value.r, ig.ImGuiColorEditFlags_None);
|
||||
_ = ig.igText("Dear ImGui Version: %s", ig.IMGUI_VERSION);
|
||||
}
|
||||
ig.igEnd();
|
||||
|
||||
ig.igEnd();
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// if (ig.igBeginMainMenuBar()) {
|
||||
// ig.igEndMainMenuBar();
|
||||
// }
|
||||
//=== UI CODE ENDS HERE
|
||||
|
||||
// var frame = Platform.Frame{
|
||||
// .platform = &self.exposed,
|
||||
// .input = .{},
|
||||
// .output = .{},
|
||||
// };
|
||||
// if (self.frame(&frame)) {
|
||||
// // ?
|
||||
// } else |e| {
|
||||
// log.err("frame() failed: {}", .{e});
|
||||
// if (@errorReturnTrace()) |trace| {
|
||||
// std.debug.dumpErrorReturnTrace(trace);
|
||||
// }
|
||||
// self.quit();
|
||||
// }
|
||||
|
||||
// var pass_action: sg.PassAction = .{};
|
||||
// pass_action.colors[0] = .{
|
||||
// .load_action = .CLEAR,
|
||||
// .clear_value = toSokolColor(.black)
|
||||
// };
|
||||
|
||||
sg.beginPass(.{ .action = self.pass_action, .swapchain = sglue.swapchain() });
|
||||
simgui.render();
|
||||
sg.endPass();
|
||||
sg.commit();
|
||||
}
|
||||
|
||||
fn sokolEvent(ev: [*c]const sapp.Event, user_data: ?*anyopaque) callconv(.c) void {
|
||||
const self: *Self = @ptrCast(@alignCast(user_data));
|
||||
_ = self; // autofix
|
||||
|
||||
// forward input events to sokol-imgui
|
||||
_ = simgui.handleEvent(ev.*);
|
||||
}
|
||||
|
||||
fn sokolCleanup(user_data: ?*anyopaque) callconv(.c) void {
|
||||
const self: *Self = @ptrCast(@alignCast(user_data));
|
||||
_ = self; // autofix
|
||||
// self.deinit(&self.exposed);
|
||||
simgui.shutdown();
|
||||
sg.shutdown();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const RunOptions = struct {
|
||||
init: std.process.Init,
|
||||
|
||||
window_title: [*c]const u8 = null,
|
||||
width: i32 = 800,
|
||||
height: i32 = 600,
|
||||
};
|
||||
|
||||
pub fn run(App: type, opts: RunOptions) void {
|
||||
const arena = opts.init.arena.allocator();
|
||||
const gpa = opts.init.gpa;
|
||||
_ = gpa; // autofix
|
||||
|
||||
const Platform = PlatformType(App);
|
||||
const plt = arena.create(Platform) catch @panic("OOM");
|
||||
plt.* = Platform{
|
||||
.cli_args = opts.init.minimal.args.toSlice(arena) catch @panic("OOM")
|
||||
};
|
||||
|
||||
// var init = Platform.Init{
|
||||
// .cli_args = self.cli_args
|
||||
// };
|
||||
// if (self.init(&self.exposed, &init)) |maybe_error_code| {
|
||||
// if (maybe_error_code) |error_code| {
|
||||
// std.process.exit(error_code);
|
||||
// }
|
||||
// } else |e| {
|
||||
// log.err("init() failed: {}", .{e});
|
||||
// if (@errorReturnTrace()) |trace| {
|
||||
// std.debug.dumpErrorReturnTrace(trace);
|
||||
// }
|
||||
// std.process.exit(1);
|
||||
// }
|
||||
|
||||
if (builtin.os.tag == .linux) {
|
||||
var sa: std.posix.Sigaction = .{
|
||||
.handler = .{ .handler = posixSignalHandler },
|
||||
.mask = std.posix.sigemptyset(),
|
||||
.flags = std.posix.SA.RESTART,
|
||||
};
|
||||
std.posix.sigaction(std.posix.SIG.INT, &sa, null);
|
||||
}
|
||||
|
||||
var icon: sapp.IconDesc = .{};
|
||||
icon.sokol_default = true;
|
||||
// TODO: Don't hard code icon path, allow changing through options
|
||||
// var icon_data = try STBImage.load(@embedFile("../assets/icon.png"));
|
||||
// defer icon_data.deinit();
|
||||
// TODO:
|
||||
// icon.images[0] = .{
|
||||
// .width = @intCast(icon_data.width),
|
||||
// .height = @intCast(icon_data.height),
|
||||
// .pixels = .{
|
||||
// .ptr = icon_data.rgba8_pixels,
|
||||
// .size = icon_data.width * icon_data.height * 4
|
||||
// }
|
||||
// };
|
||||
|
||||
tracy.setThreadName("Main");
|
||||
|
||||
sapp.run(.{
|
||||
.init_userdata_cb = Platform.sokolInit,
|
||||
.frame_userdata_cb = Platform.sokolFrame,
|
||||
.cleanup_userdata_cb = Platform.sokolCleanup,
|
||||
.event_userdata_cb = Platform.sokolEvent,
|
||||
.user_data = plt,
|
||||
.window_title = opts.window_title,
|
||||
.width = opts.width,
|
||||
.height = opts.height,
|
||||
.icon = icon,
|
||||
.logger = .{ .func = sokolLogCallback },
|
||||
});
|
||||
}
|
||||
|
||||
fn posixSignalHandler(sig: std.posix.SIG) callconv(.c) void {
|
||||
_ = sig;
|
||||
sapp.requestQuit();
|
||||
}
|
||||
|
||||
fn toSokolColor(color: Color) sokol.gfx.Color {
|
||||
// TODO: Assert and do cast
|
||||
return sokol.gfx.Color{
|
||||
.r = color.r,
|
||||
.g = color.g,
|
||||
.b = color.b,
|
||||
.a = color.a,
|
||||
};
|
||||
}
|
||||
|
||||
fn sokolLogFmt(log_level: u32, comptime format: []const u8, args: anytype) void {
|
||||
if (log_level == 0) {
|
||||
log.err(format, args);
|
||||
} else if (log_level == 1) {
|
||||
log.err(format, args);
|
||||
} else if (log_level == 2) {
|
||||
log.warn(format, args);
|
||||
} else {
|
||||
log.info(format, args);
|
||||
}
|
||||
}
|
||||
|
||||
fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {
|
||||
return std.mem.span(c_str);
|
||||
}
|
||||
|
||||
fn sokolLogCallback(tag: [*c]const u8, log_level: u32, log_item: u32, message: [*c]const u8, line_nr: u32, filename: [*c]const u8, user_data: ?*anyopaque) callconv(.c) void {
|
||||
_ = user_data;
|
||||
|
||||
if (filename != null) {
|
||||
sokolLogFmt(
|
||||
log_level,
|
||||
"[{s}][id:{}] {s}:{}: {s}",
|
||||
.{
|
||||
cStrToZig(tag orelse "-"),
|
||||
log_item,
|
||||
std.fs.path.basename(cStrToZig(filename orelse "-")),
|
||||
line_nr,
|
||||
cStrToZig(message orelse "")
|
||||
}
|
||||
);
|
||||
} else {
|
||||
sokolLogFmt(
|
||||
log_level,
|
||||
"[{s}][id:{}] {s}",
|
||||
.{
|
||||
cStrToZig(tag orelse "-"),
|
||||
log_item,
|
||||
cStrToZig(message orelse "")
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const PluginAPI = @import("plugin_api");
|
||||
|
||||
fn hello() callconv(.c) u32 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
pub const api = PluginAPI.Plugin{
|
||||
.hello = hello
|
||||
};
|
||||
@ -1,26 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const Plugin = struct {
|
||||
hello: *const fn() callconv(.c) u32,
|
||||
|
||||
comptime {
|
||||
for (@typeInfo(Plugin).@"struct".fields) |field| {
|
||||
assert(isPointerToCFunction(field.type));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn isPointerToCFunction(T: type) bool {
|
||||
if (@typeInfo(T) != .pointer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fnInfo = @typeInfo(@typeInfo(T).pointer.child);
|
||||
if (fnInfo != .@"fn") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const calling_convention = std.meta.activeTag(fnInfo.@"fn".calling_convention);
|
||||
return calling_convention == std.builtin.CallingConvention.c;
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
const plugin = @import("plugin");
|
||||
const PluginAPI = @import("plugin_api");
|
||||
|
||||
comptime {
|
||||
if (@hasField(plugin, "api")) {
|
||||
@compileError("Missing 'api' field");
|
||||
}
|
||||
|
||||
if (@TypeOf(plugin.api) != PluginAPI.Plugin) {
|
||||
@compileError("Incorrect 'api' field type");
|
||||
}
|
||||
|
||||
for (@typeInfo(PluginAPI.Plugin).@"struct".fields) |field| {
|
||||
@export(@field(plugin.api, field.name), .{ .name = field.name });
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user