uhdk tracy integration 0.14.1

This commit is contained in:
Peter Li 2026-09-18 08:54:01 -07:00
parent d1c4f14877
commit 70f19f1652
9 changed files with 369 additions and 107 deletions

View File

@ -11,6 +11,10 @@ Xcode Command Line Tools. The Windows Win32/WGL backend cross-compiles for
`x86_64-windows-gnu`; runtime verification on Windows is pending. It requires
Windows 10 version 1703 or later and an OpenGL 3.3-capable driver.
Private profiling hooks compile away in this standalone build. Tracy support
belongs to the parent repository's static demos; it adds no dependencies or
public ABI to this package.
## Build
```sh
@ -77,3 +81,28 @@ watch completion. Input uses IMM32 composition; TSF reconversion and pen/touch
are not implemented. Native UTF-16 path bytes are kept separately from the WTF-8
paths passed to Zig I/O. If a native path cannot be exported losslessly as a shell
URI, URI export returns `UNSUPPORTED`; native-path export still preserves it.
## Optional profiling
`uhdk.h` includes `uhdk_profile.h`: scope, frame, thread-name and named allocation/
free macros. They compile away without evaluating arguments unless the consumer
defines `UHDK_PROFILE_BACKEND` to a quoted adapter header before including UHDK.
An adapter implements the macros documented in that header. Names require static
lifetime; pair zones on the same thread in nesting order and report frees before
releasing memory. Do not put allocation expressions inside profiling macros.
Internal CPU and memory instrumentation is disabled by default. A static consumer
can pass `.tracy = true` to `b.dependency("uhdk", ...)` (or `-Dtracy=true` when
building the archive) and link its own Tracy 0.14.1 client configured with
`TRACY_ENABLE` and `TRACY_ON_DEMAND`. UHDK never fetches or links Tracy itself.
Shared instrumentation is unsupported. Run standalone unit tests with the default
configuration; enabled instrumentation requires the consumer's linked client.
The full repository provides an example adapter under `examples/profiling`.
The `UHDK` memory pool measures requested Zig backing allocations, including
objects and arena blocks. Win32 explicit calloc/free blocks use a separate
`UHDK native` pool. OS/framework allocations and GPU memory are excluded. On-demand capture omits allocations made before connection, so it is not
a full live-heap snapshot. Failed allocations emit nothing; in-place resizes emit
free + alloc. Moving reallocations use alloc/copy/free to preserve event ordering.
Disabled builds use the original allocator directly.

101
build.zig
View File

@ -5,42 +5,10 @@ pub fn build(b: *std.Build) void {
if (target.result.os.tag != .macos and target.result.os.tag != .windows) @panic("Uhdk supports macOS and Windows.");
const optimize = b.standardOptimizeOption(.{});
const shared = b.option(bool, "shared", "Build libuhdk as a shared library instead of static") orelse false;
const module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
const native = b.addTranslateC(.{
.root_source_file = b.path("src/native.h"),
.target = target,
.optimize = optimize,
});
native.addIncludePath(b.path("include"));
module.addImport("native", native.createModule());
module.addIncludePath(b.path("include"));
module.addIncludePath(b.path("src"));
if (target.result.os.tag == .macos) {
module.addCSourceFile(.{
.file = b.path("src/macos/native.m"),
.flags = &.{
"-fobjc-arc", "-fblocks", "-Wno-deprecated-declarations",
"-Wall", "-Wextra", "-Werror",
},
});
module.linkFramework("Cocoa", .{});
module.linkFramework("OpenGL", .{});
module.linkFramework("CoreServices", .{});
} else {
module.addCSourceFile(.{ .file = b.path("src/windows/native.c"), .flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror", "-DUNICODE", "-D_UNICODE" } });
for ([_][]const u8{ "user32", "gdi32", "opengl32", "shell32", "ole32", "uuid", "imm32", "comctl32", "shlwapi", "advapi32" }) |name| module.linkSystemLibrary(name, .{});
}
module.addCSourceFile(.{ .file = b.path("src/glad.c"), .flags = if (shared) &.{ "-std=c99", "-DGLAD_API_CALL_EXPORT", "-DGLAD_API_CALL_EXPORT_BUILD" } else &.{"-std=c99"} });
const lib = b.addLibrary(.{
.name = "uhdk",
.linkage = if (shared) .dynamic else .static,
.root_module = module,
});
const tracy = b.option(bool, "tracy", "Emit Tracy 0.14.1 on-demand hooks; consumer must link the client") orelse false;
if (tracy and shared) @panic("Tracy instrumentation requires static linking");
const lib = createLibraryNamed(b, target, optimize, shared, tracy, "uhdk");
const module = lib.root_module;
const install_lib = b.addInstallArtifact(lib, .{});
const install_headers = b.addInstallDirectory(.{
.source_dir = b.path("include"),
@ -72,3 +40,64 @@ pub fn build(b: *std.Build) void {
});
public_module.linkLibrary(lib);
}
// The parent development build uses this to create a separate, uninstalled
// instrumented archive. Standalone consumers may opt in and supply their client.
pub fn createLibrary(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, shared: bool, instrumented: bool) *std.Build.Step.Compile {
return createLibraryNamed(b, target, optimize, shared, instrumented, if (instrumented) "uhdk-instrumented" else "uhdk");
}
fn createLibraryNamed(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, shared: bool, instrumented: bool, artifact_name: []const u8) *std.Build.Step.Compile {
std.debug.assert(!shared or !instrumented);
const module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
const native = b.addTranslateC(.{
.root_source_file = b.path("src/native.h"),
.target = target,
.optimize = optimize,
});
native.addIncludePath(b.path("include"));
module.addImport("native", native.createModule());
module.addImport("profile", createProfileModule(b, target, optimize, instrumented));
module.addIncludePath(b.path("include"));
module.addIncludePath(b.path("src"));
if (instrumented) module.addCMacro("UHDK_INTERNAL_TRACY", "1");
if (target.result.os.tag == .macos) {
module.addCSourceFile(.{
.file = b.path("src/macos/native.m"),
.flags = &.{
"-fobjc-arc", "-fblocks", "-Wno-deprecated-declarations",
"-Wall", "-Wextra", "-Werror",
},
});
module.linkFramework("Cocoa", .{});
module.linkFramework("OpenGL", .{});
module.linkFramework("CoreServices", .{});
} else {
module.addCSourceFile(.{ .file = b.path("src/windows/native.c"), .flags = &.{ "-std=c11", "-Wall", "-Wextra", "-Werror", "-DUNICODE", "-D_UNICODE" } });
for ([_][]const u8{ "user32", "gdi32", "opengl32", "shell32", "ole32", "uuid", "imm32", "comctl32", "shlwapi", "advapi32" }) |name| module.linkSystemLibrary(name, .{});
}
module.addCSourceFile(.{ .file = b.path("src/glad.c"), .flags = if (shared) &.{ "-std=c99", "-DGLAD_API_CALL_EXPORT", "-DGLAD_API_CALL_EXPORT_BUILD" } else &.{"-std=c99"} });
const lib = b.addLibrary(.{
.name = artifact_name,
.linkage = if (shared) .dynamic else .static,
.root_module = module,
});
return lib;
}
pub fn createProfileModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, enabled: bool) *std.Build.Module {
const options = b.addOptions();
options.addOption(bool, "enabled", enabled);
const profile = b.createModule(.{
.root_source_file = b.path("src/Profile.zig"),
.target = target,
.optimize = optimize,
});
profile.addOptions("profile_options", options);
return profile;
}

View File

@ -48,6 +48,7 @@
*/
#include <stdint.h>
#include "uhdk_profile.h"
#ifndef UHDK_API
#if defined(_WIN32) && defined(UHDK_SHARED)

28
include/uhdk_profile.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef UHDK_PROFILE_H_INCLUDED
#define UHDK_PROFILE_H_INCLUDED
/* Optional, consumer-owned instrumentation; no exported UHDK ABI or dependency.
* Define UHDK_PROFILE_BACKEND to a quoted adapter header before including uhdk.h.
* That header implements the macros below. Without it, arguments are not evaluated.
* Names (zone, thread, pool) must be string literals or have static lifetime.
* C zones: UHDK_PROFILE_BEGIN(token, "name"); ... UHDK_PROFILE_END(token);
* End on the same thread, in reverse nesting order, including early exits.
* C++ may use UHDK_PROFILE_SCOPE("name") for automatic scope exit.
* Memory: report successful non-null allocations, then free BEFORE releasing
* storage. A successful resize is free + alloc; failed allocations emit nothing.
* A pointer must use the same pool name for allocation and free. These hooks do
* not allocate/free memory themselves, intercept malloc, or transfer ownership.
*/
#ifdef UHDK_PROFILE_BACKEND
#include UHDK_PROFILE_BACKEND
#else
#define UHDK_PROFILE_BEGIN(token, name) ((void)0)
#define UHDK_PROFILE_END(token) ((void)0)
#define UHDK_PROFILE_SCOPE(name) ((void)0)
#define UHDK_PROFILE_FRAME() ((void)0)
#define UHDK_PROFILE_THREAD(name) ((void)0)
#define UHDK_PROFILE_ALLOC(ptr, size, pool) ((void)0)
#define UHDK_PROFILE_FREE(ptr, pool) ((void)0)
#endif
#endif

137
src/Profile.zig Normal file
View File

@ -0,0 +1,137 @@
//! Private, compile-time instrumentation. Standalone UHDK builds disable it.
const std = @import("std");
const builtin = @import("builtin");
pub const enabled = @import("profile_options").enabled;
const verify_stack_order = builtin.mode == .Debug or builtin.mode == .ReleaseSafe;
// Tracy 0.14.1 C ABI with TRACY_ON_DEMAND, linked only by the full repo's demos.
// No C header translation or references to Tracy symbols in disabled builds.
const SourceLocation = extern struct {
name: [*:0]const u8,
function: [*:0]const u8,
file: [*:0]const u8,
line: u32,
color: u32 = 0,
};
const Token = extern struct {
id: u32 = 0,
active: i32 = 0,
connection: u64 = 0,
};
extern fn ___tracy_emit_zone_begin(source: *const SourceLocation, active: i32) Token;
extern fn ___tracy_emit_zone_end(zone: Token) void;
extern fn ___tracy_emit_frame_mark(name: ?[*:0]const u8) void;
extern fn ___tracy_set_thread_name(name: [*:0]const u8) void;
threadlocal var stack_depth: usize = 0;
pub const Zone = if (enabled) struct {
token: Token,
depth: if (verify_stack_order) usize else void,
owner: if (verify_stack_order) std.Thread.Id else void,
pub inline fn end(self: @This()) void {
if (verify_stack_order) {
std.debug.assert(self.owner == std.Thread.getCurrentId());
std.debug.assert(self.depth == stack_depth);
stack_depth -= 1;
}
if (self.token.active != 0) ___tracy_emit_zone_end(self.token);
}
} else struct {
pub inline fn end(_: @This()) void {}
};
// The complete @src() (including column) specializes this type. Mutable static
// storage guarantees a unique address even when two records have equal fields.
// It is initialized at comptime and never rewritten, so threads can share it.
fn StaticSource(comptime source: std.builtin.SourceLocation, comptime name: [:0]const u8) type {
return struct {
var location: SourceLocation = .{
.name = name,
.function = source.fn_name,
.file = source.file,
.line = source.line,
};
};
}
pub inline fn begin(comptime source: std.builtin.SourceLocation, comptime name: [:0]const u8) Zone {
if (!enabled) return .{};
const Statics = StaticSource(source, name);
const token = ___tracy_emit_zone_begin(&Statics.location, 1);
if (verify_stack_order) stack_depth += 1;
return .{
// Tracy leaves id/connection unspecified for inactive zones.
.token = if (token.active != 0) token else .{},
.depth = if (verify_stack_order) stack_depth else {},
.owner = if (verify_stack_order) std.Thread.getCurrentId() else {},
};
}
pub inline fn frame() void {
if (enabled) ___tracy_emit_frame_mark(null);
}
pub inline fn thread(comptime name: [:0]const u8) void {
if (enabled) ___tracy_set_thread_name(name);
}
extern fn ___tracy_emit_memory_alloc_named(ptr: *const anyopaque, size: usize, name: [*:0]const u8) void;
extern fn ___tracy_emit_memory_free_named(ptr: *const anyopaque, name: [*:0]const u8) void;
pub inline fn alloc(ptr: *const anyopaque, size: usize, comptime pool: [:0]const u8) void {
if (enabled) ___tracy_emit_memory_alloc_named(ptr, size, pool);
}
pub inline fn free(ptr: *const anyopaque, comptime pool: [:0]const u8) void {
if (enabled) ___tracy_emit_memory_free_named(ptr, pool);
}
/// Keep this wrapper alive and at a stable address while its allocator is used.
/// No wrapper or instrumentation overhead when disabled. Tracks backing blocks,
/// not arena suballocations. Thread safety is inherited from the child allocator.
pub fn TrackingAllocator(comptime pool: [:0]const u8) type {
return struct {
child: std.mem.Allocator,
const Self = @This();
pub fn allocator(self: *Self) std.mem.Allocator {
if (!enabled) return self.child;
return .{
.ptr = self,
.vtable = &.{
.alloc = allocate,
.resize = resize,
// Let Allocator.realloc use alloc/copy/free. A moving rawRemap
// releases the old address before we can report its free, so
// another thread could reuse it and emit an out-of-order alloc.
.remap = std.mem.Allocator.noRemap,
.free = release,
},
};
}
fn allocate(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ret: usize) ?[*]u8 {
const self: *Self = @ptrCast(@alignCast(ctx));
const ptr = self.child.rawAlloc(len, alignment, ret) orelse return null;
alloc(ptr, len, pool);
return ptr;
}
fn resize(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, len: usize, ret: usize) bool {
const self: *Self = @ptrCast(@alignCast(ctx));
if (!self.child.rawResize(memory, alignment, len, ret)) return false;
free(memory.ptr, pool);
alloc(memory.ptr, len, pool);
return true;
}
fn release(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret: usize) void {
const self: *Self = @ptrCast(@alignCast(ctx));
free(memory.ptr, pool);
self.child.rawFree(memory, alignment, ret);
}
};
}
var uhdk_allocator: TrackingAllocator("UHDK") = .{ .child = std.heap.smp_allocator };
pub fn allocator() std.mem.Allocator {
return uhdk_allocator.allocator();
}

View File

@ -1,5 +1,6 @@
//! C ABI entry points for app.
const std = @import("std");
const profile = @import("profile");
const backend_kind = if (@import("builtin").os.tag == .windows) c.UHDK_BACKEND_WINDOWS else c.UHDK_BACKEND_MACOS;
const c = @import("native");
const runtime = @import("../runtime.zig");
@ -22,7 +23,7 @@ pub export fn uhdk_app_create(desc: [*c]const c.uhdk_app_desc, out: [*c]?*c.uhdk
!abi.validText(desc.*.application_id, true)) return abi.INVALID;
if (desc.*.preferred_backend != c.UHDK_BACKEND_AUTO and desc.*.preferred_backend != backend_kind) return c.UHDK_STATUS_UNSUPPORTED;
if (singleton) return c.UHDK_STATUS_BUSY;
const gpa = std.heap.smp_allocator;
const gpa = profile.allocator();
const backend = gpa.create(std.Io.Threaded) catch return abi.OOM;
backend.* = .init(gpa, .{ .async_limit = .nothing, .concurrent_limit = .nothing });
const application = Application.create(gpa, backend.io(), abi.textBytes(desc.*.display_name)) catch {
@ -45,6 +46,7 @@ pub export fn uhdk_app_run(
if (c.z_native_is_main_thread() == 0) return c.UHDK_STATUS_WRONG_THREAD;
const application = runtime.fromAppHandle(handle);
if (application.has_run) return abi.UNAVAILABLE;
profile.thread("UHDK platform");
runtime.simpleEvent(application, c.UHDK_EVENT_READY);
const status = c.z_native_start(handle, abi.textSpan(application.name));
if (status != abi.OK) return status;
@ -94,6 +96,8 @@ pub export fn uhdk_app_poll_events(
if (handle == null or out == null) return abi.INVALID;
const application = runtime.fromAppHandle(handle);
if (runtime.checkThread(application) != abi.OK) return c.UHDK_STATUS_WRONG_THREAD;
const poll_zone = profile.begin(@src(), "UHDK poll events (includes wait)");
defer poll_zone.end();
runtime.destroyEvents(application.published);
application.published = null;
application.events.clearRetainingCapacity();
@ -123,6 +127,8 @@ pub export fn uhdk_app_poll_events(
const ns = wake_time -| current;
application.available.reset();
application.unlock();
const wait_zone = profile.begin(@src(), "UHDK wait for events");
defer wait_zone.end();
application.available.waitTimeout(application.io, .{ .duration = .{
.raw = .fromNanoseconds(ns),
.clock = .awake,

View File

@ -1,5 +1,6 @@
//! C ABI entry points for graphics.
const std = @import("std");
const profile = @import("profile");
const c = @import("native");
const runtime = @import("../runtime.zig");
const abi = @import("../abi.zig");
@ -30,6 +31,8 @@ pub export fn uhdk_gl_context_destroy(handle: ?*c.uhdk_gl_context) void {
}
pub export fn uhdk_gl_context_begin_frame(handle: ?*c.uhdk_gl_context) abi.Status {
const zone = profile.begin(@src(), "UHDK acquire GL context");
defer zone.end();
if (handle == null) return abi.INVALID;
const object = runtime.fromObjectHandle(handle);
if (object.payload.gl.bracket) return c.UHDK_STATUS_BUSY;
@ -91,6 +94,8 @@ pub export fn uhdk_gl_context_get_version(
}
pub export fn uhdk_gl_context_swap_buffers(handle: ?*c.uhdk_gl_context) abi.Status {
const zone = profile.begin(@src(), "UHDK present (includes vsync)");
defer zone.end();
if (handle == null) return abi.INVALID;
const object = runtime.fromObjectHandle(handle);
const valid = runtime.checkObject(object, c.Z_KIND_GL);

View File

@ -1,5 +1,6 @@
//! Queue handoff and platform scheduling.
const std = @import("std");
const profile = @import("profile");
const c = @import("native");
const abi = @import("abi.zig");
const Event = @import("Event.zig");
@ -288,6 +289,7 @@ pub fn finish(
}
pub fn applicationMain(application: *Application) void {
profile.thread("UHDK application/render");
const pool = c.z_native_pool_push();
defer c.z_native_pool_pop(pool);
application.lock();
@ -451,6 +453,8 @@ pub fn fileError(err: anyerror) abi.Status {
}
pub fn fileStep(application: *Application, command: *Request) bool {
const zone = profile.begin(@src(), "UHDK file operation batch");
defer zone.end();
if (isCancelled(application, command)) {
finish(application, command, abi.CANCELLED, null);
return true;
@ -611,6 +615,8 @@ pub fn complete(
}
pub fn tick(handle: ?*c.uhdk_app) void {
const zone = profile.begin(@src(), "UHDK platform dispatch");
defer zone.end();
const application = fromAppHandle(handle);
// Native tracking loops can reenter the run loop; never reenter application command.
if (application.in_tick) return;

View File

@ -16,6 +16,27 @@
#include "native.h"
#include "glad/gl.h"
/* Explicit native-owned heap blocks. Framework/COM/OS allocations retain their
* own ownership APIs and are not included in this pool. */
#if defined(UHDK_INTERNAL_TRACY)
extern void ___tracy_emit_memory_alloc_named(const void *ptr, size_t size, const char *name);
extern void ___tracy_emit_memory_free_named(const void *ptr, const char *name);
static void *profileCalloc(size_t count, size_t size) {
void *ptr = calloc(count, size);
if (ptr)
___tracy_emit_memory_alloc_named(ptr, count * size, "UHDK native");
return ptr;
}
static void profileFree(void *ptr) {
if (ptr)
___tracy_emit_memory_free_named(ptr, "UHDK native");
free(ptr);
}
#else
#define profileCalloc calloc
#define profileFree free
#endif
#define Z_WAKE (WM_APP + 1)
#define Z_DIALOG_MESSAGE (WM_APP + 2)
#define Z_POPUP_MESSAGE (WM_APP + 3)
@ -103,14 +124,14 @@ static WCHAR *wide(uhdk_text text) {
: 0;
if (text.len && !n)
return NULL;
WCHAR *s = calloc((size_t)n + 1, sizeof(WCHAR));
WCHAR *s = profileCalloc((size_t)n + 1, sizeof(WCHAR));
if (s && n)
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.ptr, (int)text.len, s, n);
return s;
}
static char *utf8(const WCHAR *text, int len, uint64_t *out_len) {
int n = len ? WideCharToMultiByte(CP_UTF8, 0, text, len, NULL, 0, NULL, NULL) : 0;
char *s = calloc((size_t)n + 1, 1);
char *s = profileCalloc((size_t)n + 1, 1);
if (s && n)
WideCharToMultiByte(CP_UTF8, 0, text, len, s, n, NULL, NULL);
*out_len = (uint64_t)n;
@ -118,7 +139,7 @@ static char *utf8(const WCHAR *text, int len, uint64_t *out_len) {
}
static WCHAR *filePath(uhdk_file *file) {
uhdk_bytes bytes = z_file_path(file);
WCHAR *s = calloc((size_t)bytes.len / 2 + 1, sizeof(WCHAR));
WCHAR *s = profileCalloc((size_t)bytes.len / 2 + 1, sizeof(WCHAR));
if (s && bytes.len)
memcpy(s, bytes.ptr, (size_t)bytes.len);
return s;
@ -200,11 +221,11 @@ static void textEvent(Window *w, uint32_t type, const WCHAR *text, int length, i
char *prefix = utf8(text, cursor, &position);
if (prefix) {
e.data.text.selection_start = position;
free(prefix);
profileFree(prefix);
}
}
z_emit(host.app, &e);
free(s);
profileFree(s);
}
static void editEvent(Window *w, uint32_t command) {
uhdk_event e = {0};
@ -394,24 +415,24 @@ static void composition(Window *w, LPARAM flags) {
LONG n = ImmGetCompositionStringW(ime, kinds[i], NULL, 0);
if (n < 0)
continue;
WCHAR *s = calloc((size_t)n + 2, 1);
WCHAR *s = profileCalloc((size_t)n + 2, 1);
if (!s)
continue;
ImmGetCompositionStringW(ime, kinds[i], s, (DWORD)n);
int cursor = i ? ImmGetCompositionStringW(ime, GCS_CURSORPOS, NULL, 0) : -1;
textEvent(w, i ? UHDK_EVENT_TEXT_PREEDIT : UHDK_EVENT_TEXT_COMMIT, s, n / 2, cursor);
free(s);
profileFree(s);
}
ImmReleaseContext(w->hwnd, ime);
}
static void dropFiles(Window *w, HDROP drop) {
UINT count = DragQueryFileW(drop, UINT_MAX, NULL, 0);
uhdk_file **files = calloc(count ? count : 1, sizeof(*files));
uhdk_file **files = profileCalloc(count ? count : 1, sizeof(*files));
uint64_t used = 0;
if (files)
for (UINT i = 0; i < count; ++i) {
UINT n = DragQueryFileW(drop, i, NULL, 0);
WCHAR *p = calloc((size_t)n + 1, sizeof(WCHAR));
WCHAR *p = profileCalloc((size_t)n + 1, sizeof(WCHAR));
if (!p)
break;
DragQueryFileW(drop, i, p, n + 1);
@ -419,7 +440,7 @@ static void dropFiles(Window *w, HDROP drop) {
z_file_from_url(host.app, (uhdk_bytes){(uint8_t *)p, (uint64_t)n * 2}, NULL);
if (file)
files[used++] = file;
free(p);
profileFree(p);
}
POINT point;
DragQueryPoint(drop, &point);
@ -433,7 +454,7 @@ static void dropFiles(Window *w, HDROP drop) {
}
for (uint64_t i = 0; i < used; ++i)
uhdk_file_release(files[i]);
free(files);
profileFree(files);
DragFinish(drop);
}
static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
@ -741,13 +762,13 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
if (path.len) {
if (path.len % 2 || path.len > 65534)
return UHDK_STATUS_INVALID_ARGUMENT;
name = calloc((size_t)path.len / 2 + 1, sizeof(WCHAR));
name = profileCalloc((size_t)path.len / 2 + 1, sizeof(WCHAR));
if (!name)
return UHDK_STATUS_OUT_OF_MEMORY;
memcpy(name, path.ptr, (size_t)path.len);
for (size_t i = 0; i < path.len / 2; i++)
if (!name[i]) {
free(name);
profileFree(name);
return UHDK_STATUS_INVALID_ARGUMENT;
}
} else {
@ -755,19 +776,19 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
if (!url)
return UHDK_STATUS_INVALID_ARGUMENT;
if (_wcsnicmp(url, L"file:", 5)) {
free(url);
profileFree(url);
return UHDK_STATUS_INVALID_ARGUMENT;
}
DWORD capacity = 32768;
name = calloc(capacity, sizeof(WCHAR));
name = profileCalloc(capacity, sizeof(WCHAR));
if (!name) {
free(url);
profileFree(url);
return UHDK_STATUS_OUT_OF_MEMORY;
}
HRESULT hr = PathCreateFromUrlW(url, name, &capacity, 0);
free(url);
profileFree(url);
if (FAILED(hr)) {
free(name);
profileFree(name);
return UHDK_STATUS_INVALID_ARGUMENT;
}
}
@ -777,13 +798,13 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
name[1] == L':' && (name[2] == L'\\' || name[2] == L'/');
BOOL unc = n >= 3 && name[0] == L'\\' && name[1] == L'\\';
if (!drive && !unc) {
free(name);
profileFree(name);
return UHDK_STATUS_INVALID_ARGUMENT;
}
DWORD uri_capacity = 3 * 32768;
WCHAR *url = calloc(uri_capacity, sizeof(WCHAR));
WCHAR *url = profileCalloc(uri_capacity, sizeof(WCHAR));
if (!url) {
free(name);
profileFree(name);
return UHDK_STATUS_OUT_OF_MEMORY;
}
HRESULT hr = UrlCreateFromPathW(name, url, &uri_capacity, 0);
@ -802,10 +823,10 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
display ? z_file_init(file, (uhdk_bytes){(uint8_t *)name, n * 2},
(uhdk_text){url8, uri_len}, (uhdk_text){display, display_len}, NULL)
: UHDK_STATUS_OS_ERROR;
free(display);
free(url8);
free(url);
free(name);
profileFree(display);
profileFree(url8);
profileFree(url);
profileFree(name);
return status;
}
@ -819,13 +840,13 @@ static PROC glProc(const char *name) {
static uhdk_status createGraphics(Window *w, void *handle) {
if (!(w->flags & UHDK_WINDOW_OPENGL))
return UHDK_STATUS_INVALID_ARGUMENT;
Graphics *g = calloc(1, sizeof(*g));
Graphics *g = profileCalloc(1, sizeof(*g));
if (!g)
return UHDK_STATUS_OUT_OF_MEMORY;
g->window = w->hwnd;
g->dc = GetDC(w->hwnd);
if (!g->dc) {
free(g);
profileFree(g);
return UHDK_STATUS_OS_ERROR;
}
PIXELFORMATDESCRIPTOR pfd = {0};
@ -888,7 +909,7 @@ failed:
if (g->rc)
wglDeleteContext(g->rc);
ReleaseDC(g->window, g->dc);
free(g);
profileFree(g);
return UHDK_STATUS_UNSUPPORTED;
}
uhdk_status z_native_gl(void *native, uint32_t op, void *out) {
@ -982,11 +1003,11 @@ static HMENU buildMenu(uhdk_menu *source, BOOL popup) {
!InsertMenuItemW(menu, (UINT)i, TRUE, &native)) {
if (native.hSubMenu)
DestroyMenu(native.hSubMenu);
free(label);
profileFree(label);
DestroyMenu(menu);
return NULL;
}
free(label);
profileFree(label);
}
return menu;
}
@ -1087,10 +1108,10 @@ static void showDialog(void) {
IFileDialog_SetFolder(dialog, folder);
IShellItem_Release(folder);
}
free(path);
profileFree(path);
}
COMDLG_FILTERSPEC *filters =
calloc(d->filters_len ? (size_t)d->filters_len : 1, sizeof(*filters));
profileCalloc(d->filters_len ? (size_t)d->filters_len : 1, sizeof(*filters));
BOOL filter_ok = filters != NULL;
if (filters)
for (uint64_t i = 0; i < d->filters_len; i++) {
@ -1099,7 +1120,7 @@ static void showDialog(void) {
size_t capacity = 2;
for (uint64_t j = 0; j < extensions.len; j++)
capacity += (size_t)extensions.ptr[j].len + 3;
WCHAR *pattern = calloc(capacity, sizeof(WCHAR));
WCHAR *pattern = profileCalloc(capacity, sizeof(WCHAR));
filters[i].pszSpec = pattern;
if (!filters[i].pszName || !pattern) {
filter_ok = FALSE;
@ -1117,7 +1138,7 @@ static void showDialog(void) {
wcscat(pattern, L";");
wcscat(pattern, L"*.");
wcscat(pattern, ext);
free(ext);
profileFree(ext);
}
}
if (filter_ok && d->filters_len)
@ -1137,7 +1158,7 @@ static void showDialog(void) {
if (SUCCEEDED(hr)) {
DWORD n = 0;
IShellItemArray_GetCount(items, &n);
files = calloc(n ? n : 1, sizeof(*files));
files = profileCalloc(n ? n : 1, sizeof(*files));
if (files)
for (DWORD i = 0; i < n; i++) {
IShellItem *item = NULL;
@ -1162,7 +1183,7 @@ static void showDialog(void) {
status = UHDK_STATUS_OS_ERROR;
} else if (status == UHDK_STATUS_OK) {
IShellItem *item = NULL;
files = calloc(1, sizeof(*files));
files = profileCalloc(1, sizeof(*files));
if (files && SUCCEEDED(IFileDialog_GetResult(dialog, &item))) {
files[0] = shellFile(item);
IShellItem_Release(item);
@ -1178,20 +1199,20 @@ static void showDialog(void) {
host.dialog = NULL;
host.dialog_id = 0;
IFileDialog_Release(dialog);
free(title);
free(label);
free(suggestion);
profileFree(title);
profileFree(label);
profileFree(suggestion);
if (filters) {
for (uint64_t i = 0; i < d->filters_len; i++) {
free((void *)filters[i].pszName);
free((void *)filters[i].pszSpec);
profileFree((void *)filters[i].pszName);
profileFree((void *)filters[i].pszSpec);
}
free(filters);
profileFree(filters);
}
z_complete(host.app, id, status, &result);
for (uint64_t i = 0; i < count; i++)
uhdk_file_release(files[i]);
free(files);
profileFree(files);
}
static BOOL armWatch(Watch *watch) {
@ -1207,15 +1228,15 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
WCHAR *path = filePath(root);
if (!path)
return UHDK_STATUS_OUT_OF_MEMORY;
Watch *watch = calloc(1, sizeof(*watch));
Watch *watch = profileCalloc(1, sizeof(*watch));
if (!watch) {
free(path);
profileFree(path);
return UHDK_STATUS_OUT_OF_MEMORY;
}
DWORD attributes = GetFileAttributesW(path);
if (attributes == INVALID_FILE_ATTRIBUTES) {
free(path);
free(watch);
profileFree(path);
profileFree(watch);
return osError();
}
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
@ -1224,14 +1245,14 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
if (other && (!slash || other > slash))
slash = other;
if (!slash) {
free(path);
free(watch);
profileFree(path);
profileFree(watch);
return UHDK_STATUS_INVALID_ARGUMENT;
}
watch->leaf = _wcsdup(slash + 1);
if (!watch->leaf) {
free(path);
free(watch);
profileFree(path);
profileFree(watch);
return UHDK_STATUS_OUT_OF_MEMORY;
}
slash[1] = 0;
@ -1239,7 +1260,7 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
watch->directory = CreateFileW(
path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
free(path);
profileFree(path);
watch->overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
watch->handle = handle;
if (watch->directory == INVALID_HANDLE_VALUE || !watch->overlapped.hEvent || !armWatch(watch)) {
@ -1248,8 +1269,8 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
CloseHandle(watch->directory);
if (watch->overlapped.hEvent)
CloseHandle(watch->overlapped.hEvent);
free(watch->leaf);
free(watch);
profileFree(watch->leaf);
profileFree(watch);
return status;
}
watch->next = host.watches;
@ -1462,7 +1483,7 @@ void z_native_destroy(uint32_t kind, void *native) {
ReleaseDC(g->window, g->dc);
LeaveCriticalSection(&g->lock);
DeleteCriticalSection(&g->lock);
free(g);
profileFree(g);
} else if (kind == Z_KIND_WINDOW) {
Window *w = native;
Window **link = &host.windows;
@ -1481,7 +1502,7 @@ void z_native_destroy(uint32_t kind, void *native) {
DestroyWindow(w->hwnd);
if (w->menu_source)
uhdk_menu_release(w->menu_source);
free(w);
profileFree(w);
} else if (kind == Z_KIND_WATCH) {
Watch *w = native;
Watch **link = &host.watches;
@ -1496,8 +1517,8 @@ void z_native_destroy(uint32_t kind, void *native) {
}
CloseHandle(w->directory);
CloseHandle(w->overlapped.hEvent);
free(w->leaf);
free(w);
profileFree(w->leaf);
profileFree(w);
}
}
static uhdk_status clipboardWrite(uhdk_text text) {
@ -1509,18 +1530,18 @@ static uhdk_status clipboardWrite(uhdk_text text) {
size_t length = (wcslen(value) + 1) * sizeof(WCHAR);
HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, length);
if (!memory) {
free(value);
profileFree(value);
return UHDK_STATUS_OUT_OF_MEMORY;
}
void *bytes = GlobalLock(memory);
if (!bytes) {
GlobalFree(memory);
free(value);
profileFree(value);
return UHDK_STATUS_OS_ERROR;
}
memcpy(bytes, value, length);
GlobalUnlock(memory);
free(value);
profileFree(value);
if (!OpenClipboard(host.dispatch)) {
GlobalFree(memory);
return UHDK_STATUS_BUSY;
@ -1554,13 +1575,13 @@ static uhdk_status clipboardRead(uint64_t id, uint64_t limit) {
if (!text)
return UHDK_STATUS_OS_ERROR;
if (len > limit) {
free(text);
profileFree(text);
return UHDK_STATUS_BUFFER_TOO_SMALL;
}
uhdk_request_result result = {0};
result.value.text = (uhdk_text){text, len};
z_complete(host.app, id, UHDK_STATUS_OK, &result);
free(text);
profileFree(text);
return Z_DEFERRED;
}
static uhdk_status setIcon(const uhdk_image *image) {
@ -1617,7 +1638,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
cmd->target && z_object_kind(cmd->target) == Z_KIND_WINDOW ? windowOf(cmd->target) : NULL;
switch (cmd->op) {
case Z_WINDOW: {
w = calloc(1, sizeof(*w));
w = profileCalloc(1, sizeof(*w));
if (!w)
return UHDK_STATUS_OUT_OF_MEMORY;
w->handle = cmd->target;
@ -1638,16 +1659,16 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
AdjustWindowRectExForDpi(&r, style, FALSE, 0, dpi);
WCHAR *title = wide(cmd->window.title);
if (!title) {
free(w);
profileFree(w);
return UHDK_STATUS_OUT_OF_MEMORY;
}
HWND parent = cmd->window.parent ? windowOf(cmd->window.parent)->hwnd : NULL;
HWND hwnd = CreateWindowExW(0, window_class, title, style, CW_USEDEFAULT, CW_USEDEFAULT,
r.right - r.left, r.bottom - r.top, parent, NULL,
GetModuleHandleW(NULL), w);
free(title);
profileFree(title);
if (!hwnd) {
free(w);
profileFree(w);
return osError();
}
ImmAssociateContextEx(hwnd, NULL, 0);
@ -1668,7 +1689,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
if (!title)
return UHDK_STATUS_OUT_OF_MEMORY;
BOOL ok = SetWindowTextW(w->hwnd, title);
free(title);
profileFree(title);
return ok ? UHDK_STATUS_OK : osError();
}
case Z_SIZE: {
@ -1831,11 +1852,11 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
if (!url)
return UHDK_STATUS_OUT_OF_MEMORY;
if (!UrlIsW(url, URLIS_URL)) {
free(url);
profileFree(url);
return UHDK_STATUS_INVALID_ARGUMENT;
}
INT_PTR status = (INT_PTR)ShellExecuteW(NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL);
free(url);
profileFree(url);
return status > 32 ? UHDK_STATUS_OK : UHDK_STATUS_OS_ERROR;
}
case Z_WATCH: {