uhdk tracy integration 0.14.1
This commit is contained in:
parent
d1c4f14877
commit
70f19f1652
29
README.md
29
README.md
|
|
@ -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
|
`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.
|
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
|
## Build
|
||||||
|
|
||||||
```sh
|
```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
|
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
|
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.
|
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
101
build.zig
|
|
@ -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.");
|
if (target.result.os.tag != .macos and target.result.os.tag != .windows) @panic("Uhdk supports macOS and Windows.");
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
const shared = b.option(bool, "shared", "Build libuhdk as a shared library instead of static") orelse false;
|
const shared = b.option(bool, "shared", "Build libuhdk as a shared library instead of static") orelse false;
|
||||||
const module = b.createModule(.{
|
const tracy = b.option(bool, "tracy", "Emit Tracy 0.14.1 on-demand hooks; consumer must link the client") orelse false;
|
||||||
.root_source_file = b.path("src/root.zig"),
|
if (tracy and shared) @panic("Tracy instrumentation requires static linking");
|
||||||
.target = target,
|
const lib = createLibraryNamed(b, target, optimize, shared, tracy, "uhdk");
|
||||||
.optimize = optimize,
|
const module = lib.root_module;
|
||||||
.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 install_lib = b.addInstallArtifact(lib, .{});
|
const install_lib = b.addInstallArtifact(lib, .{});
|
||||||
const install_headers = b.addInstallDirectory(.{
|
const install_headers = b.addInstallDirectory(.{
|
||||||
.source_dir = b.path("include"),
|
.source_dir = b.path("include"),
|
||||||
|
|
@ -72,3 +40,64 @@ pub fn build(b: *std.Build) void {
|
||||||
});
|
});
|
||||||
public_module.linkLibrary(lib);
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "uhdk_profile.h"
|
||||||
|
|
||||||
#ifndef UHDK_API
|
#ifndef UHDK_API
|
||||||
#if defined(_WIN32) && defined(UHDK_SHARED)
|
#if defined(_WIN32) && defined(UHDK_SHARED)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! C ABI entry points for app.
|
//! C ABI entry points for app.
|
||||||
const std = @import("std");
|
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 backend_kind = if (@import("builtin").os.tag == .windows) c.UHDK_BACKEND_WINDOWS else c.UHDK_BACKEND_MACOS;
|
||||||
const c = @import("native");
|
const c = @import("native");
|
||||||
const runtime = @import("../runtime.zig");
|
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;
|
!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 (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;
|
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;
|
const backend = gpa.create(std.Io.Threaded) catch return abi.OOM;
|
||||||
backend.* = .init(gpa, .{ .async_limit = .nothing, .concurrent_limit = .nothing });
|
backend.* = .init(gpa, .{ .async_limit = .nothing, .concurrent_limit = .nothing });
|
||||||
const application = Application.create(gpa, backend.io(), abi.textBytes(desc.*.display_name)) catch {
|
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;
|
if (c.z_native_is_main_thread() == 0) return c.UHDK_STATUS_WRONG_THREAD;
|
||||||
const application = runtime.fromAppHandle(handle);
|
const application = runtime.fromAppHandle(handle);
|
||||||
if (application.has_run) return abi.UNAVAILABLE;
|
if (application.has_run) return abi.UNAVAILABLE;
|
||||||
|
profile.thread("UHDK platform");
|
||||||
runtime.simpleEvent(application, c.UHDK_EVENT_READY);
|
runtime.simpleEvent(application, c.UHDK_EVENT_READY);
|
||||||
const status = c.z_native_start(handle, abi.textSpan(application.name));
|
const status = c.z_native_start(handle, abi.textSpan(application.name));
|
||||||
if (status != abi.OK) return status;
|
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;
|
if (handle == null or out == null) return abi.INVALID;
|
||||||
const application = runtime.fromAppHandle(handle);
|
const application = runtime.fromAppHandle(handle);
|
||||||
if (runtime.checkThread(application) != abi.OK) return c.UHDK_STATUS_WRONG_THREAD;
|
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);
|
runtime.destroyEvents(application.published);
|
||||||
application.published = null;
|
application.published = null;
|
||||||
application.events.clearRetainingCapacity();
|
application.events.clearRetainingCapacity();
|
||||||
|
|
@ -123,6 +127,8 @@ pub export fn uhdk_app_poll_events(
|
||||||
const ns = wake_time -| current;
|
const ns = wake_time -| current;
|
||||||
application.available.reset();
|
application.available.reset();
|
||||||
application.unlock();
|
application.unlock();
|
||||||
|
const wait_zone = profile.begin(@src(), "UHDK wait for events");
|
||||||
|
defer wait_zone.end();
|
||||||
application.available.waitTimeout(application.io, .{ .duration = .{
|
application.available.waitTimeout(application.io, .{ .duration = .{
|
||||||
.raw = .fromNanoseconds(ns),
|
.raw = .fromNanoseconds(ns),
|
||||||
.clock = .awake,
|
.clock = .awake,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! C ABI entry points for graphics.
|
//! C ABI entry points for graphics.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const profile = @import("profile");
|
||||||
const c = @import("native");
|
const c = @import("native");
|
||||||
const runtime = @import("../runtime.zig");
|
const runtime = @import("../runtime.zig");
|
||||||
const abi = @import("../abi.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 {
|
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;
|
if (handle == null) return abi.INVALID;
|
||||||
const object = runtime.fromObjectHandle(handle);
|
const object = runtime.fromObjectHandle(handle);
|
||||||
if (object.payload.gl.bracket) return c.UHDK_STATUS_BUSY;
|
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 {
|
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;
|
if (handle == null) return abi.INVALID;
|
||||||
const object = runtime.fromObjectHandle(handle);
|
const object = runtime.fromObjectHandle(handle);
|
||||||
const valid = runtime.checkObject(object, c.Z_KIND_GL);
|
const valid = runtime.checkObject(object, c.Z_KIND_GL);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Queue handoff and platform scheduling.
|
//! Queue handoff and platform scheduling.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const profile = @import("profile");
|
||||||
const c = @import("native");
|
const c = @import("native");
|
||||||
const abi = @import("abi.zig");
|
const abi = @import("abi.zig");
|
||||||
const Event = @import("Event.zig");
|
const Event = @import("Event.zig");
|
||||||
|
|
@ -288,6 +289,7 @@ pub fn finish(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn applicationMain(application: *Application) void {
|
pub fn applicationMain(application: *Application) void {
|
||||||
|
profile.thread("UHDK application/render");
|
||||||
const pool = c.z_native_pool_push();
|
const pool = c.z_native_pool_push();
|
||||||
defer c.z_native_pool_pop(pool);
|
defer c.z_native_pool_pop(pool);
|
||||||
application.lock();
|
application.lock();
|
||||||
|
|
@ -451,6 +453,8 @@ pub fn fileError(err: anyerror) abi.Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fileStep(application: *Application, command: *Request) bool {
|
pub fn fileStep(application: *Application, command: *Request) bool {
|
||||||
|
const zone = profile.begin(@src(), "UHDK file operation batch");
|
||||||
|
defer zone.end();
|
||||||
if (isCancelled(application, command)) {
|
if (isCancelled(application, command)) {
|
||||||
finish(application, command, abi.CANCELLED, null);
|
finish(application, command, abi.CANCELLED, null);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -611,6 +615,8 @@ pub fn complete(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(handle: ?*c.uhdk_app) void {
|
pub fn tick(handle: ?*c.uhdk_app) void {
|
||||||
|
const zone = profile.begin(@src(), "UHDK platform dispatch");
|
||||||
|
defer zone.end();
|
||||||
const application = fromAppHandle(handle);
|
const application = fromAppHandle(handle);
|
||||||
// Native tracking loops can reenter the run loop; never reenter application command.
|
// Native tracking loops can reenter the run loop; never reenter application command.
|
||||||
if (application.in_tick) return;
|
if (application.in_tick) return;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,27 @@
|
||||||
#include "native.h"
|
#include "native.h"
|
||||||
#include "glad/gl.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_WAKE (WM_APP + 1)
|
||||||
#define Z_DIALOG_MESSAGE (WM_APP + 2)
|
#define Z_DIALOG_MESSAGE (WM_APP + 2)
|
||||||
#define Z_POPUP_MESSAGE (WM_APP + 3)
|
#define Z_POPUP_MESSAGE (WM_APP + 3)
|
||||||
|
|
@ -103,14 +124,14 @@ static WCHAR *wide(uhdk_text text) {
|
||||||
: 0;
|
: 0;
|
||||||
if (text.len && !n)
|
if (text.len && !n)
|
||||||
return NULL;
|
return NULL;
|
||||||
WCHAR *s = calloc((size_t)n + 1, sizeof(WCHAR));
|
WCHAR *s = profileCalloc((size_t)n + 1, sizeof(WCHAR));
|
||||||
if (s && n)
|
if (s && n)
|
||||||
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.ptr, (int)text.len, s, n);
|
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.ptr, (int)text.len, s, n);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
static char *utf8(const WCHAR *text, int len, uint64_t *out_len) {
|
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;
|
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)
|
if (s && n)
|
||||||
WideCharToMultiByte(CP_UTF8, 0, text, len, s, n, NULL, NULL);
|
WideCharToMultiByte(CP_UTF8, 0, text, len, s, n, NULL, NULL);
|
||||||
*out_len = (uint64_t)n;
|
*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) {
|
static WCHAR *filePath(uhdk_file *file) {
|
||||||
uhdk_bytes bytes = z_file_path(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)
|
if (s && bytes.len)
|
||||||
memcpy(s, bytes.ptr, (size_t)bytes.len);
|
memcpy(s, bytes.ptr, (size_t)bytes.len);
|
||||||
return s;
|
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);
|
char *prefix = utf8(text, cursor, &position);
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
e.data.text.selection_start = position;
|
e.data.text.selection_start = position;
|
||||||
free(prefix);
|
profileFree(prefix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
z_emit(host.app, &e);
|
z_emit(host.app, &e);
|
||||||
free(s);
|
profileFree(s);
|
||||||
}
|
}
|
||||||
static void editEvent(Window *w, uint32_t command) {
|
static void editEvent(Window *w, uint32_t command) {
|
||||||
uhdk_event e = {0};
|
uhdk_event e = {0};
|
||||||
|
|
@ -394,24 +415,24 @@ static void composition(Window *w, LPARAM flags) {
|
||||||
LONG n = ImmGetCompositionStringW(ime, kinds[i], NULL, 0);
|
LONG n = ImmGetCompositionStringW(ime, kinds[i], NULL, 0);
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
continue;
|
continue;
|
||||||
WCHAR *s = calloc((size_t)n + 2, 1);
|
WCHAR *s = profileCalloc((size_t)n + 2, 1);
|
||||||
if (!s)
|
if (!s)
|
||||||
continue;
|
continue;
|
||||||
ImmGetCompositionStringW(ime, kinds[i], s, (DWORD)n);
|
ImmGetCompositionStringW(ime, kinds[i], s, (DWORD)n);
|
||||||
int cursor = i ? ImmGetCompositionStringW(ime, GCS_CURSORPOS, NULL, 0) : -1;
|
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);
|
textEvent(w, i ? UHDK_EVENT_TEXT_PREEDIT : UHDK_EVENT_TEXT_COMMIT, s, n / 2, cursor);
|
||||||
free(s);
|
profileFree(s);
|
||||||
}
|
}
|
||||||
ImmReleaseContext(w->hwnd, ime);
|
ImmReleaseContext(w->hwnd, ime);
|
||||||
}
|
}
|
||||||
static void dropFiles(Window *w, HDROP drop) {
|
static void dropFiles(Window *w, HDROP drop) {
|
||||||
UINT count = DragQueryFileW(drop, UINT_MAX, NULL, 0);
|
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;
|
uint64_t used = 0;
|
||||||
if (files)
|
if (files)
|
||||||
for (UINT i = 0; i < count; ++i) {
|
for (UINT i = 0; i < count; ++i) {
|
||||||
UINT n = DragQueryFileW(drop, i, NULL, 0);
|
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)
|
if (!p)
|
||||||
break;
|
break;
|
||||||
DragQueryFileW(drop, i, p, n + 1);
|
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);
|
z_file_from_url(host.app, (uhdk_bytes){(uint8_t *)p, (uint64_t)n * 2}, NULL);
|
||||||
if (file)
|
if (file)
|
||||||
files[used++] = file;
|
files[used++] = file;
|
||||||
free(p);
|
profileFree(p);
|
||||||
}
|
}
|
||||||
POINT point;
|
POINT point;
|
||||||
DragQueryPoint(drop, &point);
|
DragQueryPoint(drop, &point);
|
||||||
|
|
@ -433,7 +454,7 @@ static void dropFiles(Window *w, HDROP drop) {
|
||||||
}
|
}
|
||||||
for (uint64_t i = 0; i < used; ++i)
|
for (uint64_t i = 0; i < used; ++i)
|
||||||
uhdk_file_release(files[i]);
|
uhdk_file_release(files[i]);
|
||||||
free(files);
|
profileFree(files);
|
||||||
DragFinish(drop);
|
DragFinish(drop);
|
||||||
}
|
}
|
||||||
static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
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) {
|
||||||
if (path.len % 2 || path.len > 65534)
|
if (path.len % 2 || path.len > 65534)
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
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)
|
if (!name)
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
memcpy(name, path.ptr, (size_t)path.len);
|
memcpy(name, path.ptr, (size_t)path.len);
|
||||||
for (size_t i = 0; i < path.len / 2; i++)
|
for (size_t i = 0; i < path.len / 2; i++)
|
||||||
if (!name[i]) {
|
if (!name[i]) {
|
||||||
free(name);
|
profileFree(name);
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -755,19 +776,19 @@ uhdk_status z_native_file_init(uhdk_file *file, uhdk_bytes path, uhdk_text uri,
|
||||||
if (!url)
|
if (!url)
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||||
if (_wcsnicmp(url, L"file:", 5)) {
|
if (_wcsnicmp(url, L"file:", 5)) {
|
||||||
free(url);
|
profileFree(url);
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
DWORD capacity = 32768;
|
DWORD capacity = 32768;
|
||||||
name = calloc(capacity, sizeof(WCHAR));
|
name = profileCalloc(capacity, sizeof(WCHAR));
|
||||||
if (!name) {
|
if (!name) {
|
||||||
free(url);
|
profileFree(url);
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
HRESULT hr = PathCreateFromUrlW(url, name, &capacity, 0);
|
HRESULT hr = PathCreateFromUrlW(url, name, &capacity, 0);
|
||||||
free(url);
|
profileFree(url);
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
free(name);
|
profileFree(name);
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
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'/');
|
name[1] == L':' && (name[2] == L'\\' || name[2] == L'/');
|
||||||
BOOL unc = n >= 3 && name[0] == L'\\' && name[1] == L'\\';
|
BOOL unc = n >= 3 && name[0] == L'\\' && name[1] == L'\\';
|
||||||
if (!drive && !unc) {
|
if (!drive && !unc) {
|
||||||
free(name);
|
profileFree(name);
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
DWORD uri_capacity = 3 * 32768;
|
DWORD uri_capacity = 3 * 32768;
|
||||||
WCHAR *url = calloc(uri_capacity, sizeof(WCHAR));
|
WCHAR *url = profileCalloc(uri_capacity, sizeof(WCHAR));
|
||||||
if (!url) {
|
if (!url) {
|
||||||
free(name);
|
profileFree(name);
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
HRESULT hr = UrlCreateFromPathW(name, url, &uri_capacity, 0);
|
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},
|
display ? z_file_init(file, (uhdk_bytes){(uint8_t *)name, n * 2},
|
||||||
(uhdk_text){url8, uri_len}, (uhdk_text){display, display_len}, NULL)
|
(uhdk_text){url8, uri_len}, (uhdk_text){display, display_len}, NULL)
|
||||||
: UHDK_STATUS_OS_ERROR;
|
: UHDK_STATUS_OS_ERROR;
|
||||||
free(display);
|
profileFree(display);
|
||||||
free(url8);
|
profileFree(url8);
|
||||||
free(url);
|
profileFree(url);
|
||||||
free(name);
|
profileFree(name);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -819,13 +840,13 @@ static PROC glProc(const char *name) {
|
||||||
static uhdk_status createGraphics(Window *w, void *handle) {
|
static uhdk_status createGraphics(Window *w, void *handle) {
|
||||||
if (!(w->flags & UHDK_WINDOW_OPENGL))
|
if (!(w->flags & UHDK_WINDOW_OPENGL))
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||||
Graphics *g = calloc(1, sizeof(*g));
|
Graphics *g = profileCalloc(1, sizeof(*g));
|
||||||
if (!g)
|
if (!g)
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
g->window = w->hwnd;
|
g->window = w->hwnd;
|
||||||
g->dc = GetDC(w->hwnd);
|
g->dc = GetDC(w->hwnd);
|
||||||
if (!g->dc) {
|
if (!g->dc) {
|
||||||
free(g);
|
profileFree(g);
|
||||||
return UHDK_STATUS_OS_ERROR;
|
return UHDK_STATUS_OS_ERROR;
|
||||||
}
|
}
|
||||||
PIXELFORMATDESCRIPTOR pfd = {0};
|
PIXELFORMATDESCRIPTOR pfd = {0};
|
||||||
|
|
@ -888,7 +909,7 @@ failed:
|
||||||
if (g->rc)
|
if (g->rc)
|
||||||
wglDeleteContext(g->rc);
|
wglDeleteContext(g->rc);
|
||||||
ReleaseDC(g->window, g->dc);
|
ReleaseDC(g->window, g->dc);
|
||||||
free(g);
|
profileFree(g);
|
||||||
return UHDK_STATUS_UNSUPPORTED;
|
return UHDK_STATUS_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
uhdk_status z_native_gl(void *native, uint32_t op, void *out) {
|
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)) {
|
!InsertMenuItemW(menu, (UINT)i, TRUE, &native)) {
|
||||||
if (native.hSubMenu)
|
if (native.hSubMenu)
|
||||||
DestroyMenu(native.hSubMenu);
|
DestroyMenu(native.hSubMenu);
|
||||||
free(label);
|
profileFree(label);
|
||||||
DestroyMenu(menu);
|
DestroyMenu(menu);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
free(label);
|
profileFree(label);
|
||||||
}
|
}
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
@ -1087,10 +1108,10 @@ static void showDialog(void) {
|
||||||
IFileDialog_SetFolder(dialog, folder);
|
IFileDialog_SetFolder(dialog, folder);
|
||||||
IShellItem_Release(folder);
|
IShellItem_Release(folder);
|
||||||
}
|
}
|
||||||
free(path);
|
profileFree(path);
|
||||||
}
|
}
|
||||||
COMDLG_FILTERSPEC *filters =
|
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;
|
BOOL filter_ok = filters != NULL;
|
||||||
if (filters)
|
if (filters)
|
||||||
for (uint64_t i = 0; i < d->filters_len; i++) {
|
for (uint64_t i = 0; i < d->filters_len; i++) {
|
||||||
|
|
@ -1099,7 +1120,7 @@ static void showDialog(void) {
|
||||||
size_t capacity = 2;
|
size_t capacity = 2;
|
||||||
for (uint64_t j = 0; j < extensions.len; j++)
|
for (uint64_t j = 0; j < extensions.len; j++)
|
||||||
capacity += (size_t)extensions.ptr[j].len + 3;
|
capacity += (size_t)extensions.ptr[j].len + 3;
|
||||||
WCHAR *pattern = calloc(capacity, sizeof(WCHAR));
|
WCHAR *pattern = profileCalloc(capacity, sizeof(WCHAR));
|
||||||
filters[i].pszSpec = pattern;
|
filters[i].pszSpec = pattern;
|
||||||
if (!filters[i].pszName || !pattern) {
|
if (!filters[i].pszName || !pattern) {
|
||||||
filter_ok = FALSE;
|
filter_ok = FALSE;
|
||||||
|
|
@ -1117,7 +1138,7 @@ static void showDialog(void) {
|
||||||
wcscat(pattern, L";");
|
wcscat(pattern, L";");
|
||||||
wcscat(pattern, L"*.");
|
wcscat(pattern, L"*.");
|
||||||
wcscat(pattern, ext);
|
wcscat(pattern, ext);
|
||||||
free(ext);
|
profileFree(ext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (filter_ok && d->filters_len)
|
if (filter_ok && d->filters_len)
|
||||||
|
|
@ -1137,7 +1158,7 @@ static void showDialog(void) {
|
||||||
if (SUCCEEDED(hr)) {
|
if (SUCCEEDED(hr)) {
|
||||||
DWORD n = 0;
|
DWORD n = 0;
|
||||||
IShellItemArray_GetCount(items, &n);
|
IShellItemArray_GetCount(items, &n);
|
||||||
files = calloc(n ? n : 1, sizeof(*files));
|
files = profileCalloc(n ? n : 1, sizeof(*files));
|
||||||
if (files)
|
if (files)
|
||||||
for (DWORD i = 0; i < n; i++) {
|
for (DWORD i = 0; i < n; i++) {
|
||||||
IShellItem *item = NULL;
|
IShellItem *item = NULL;
|
||||||
|
|
@ -1162,7 +1183,7 @@ static void showDialog(void) {
|
||||||
status = UHDK_STATUS_OS_ERROR;
|
status = UHDK_STATUS_OS_ERROR;
|
||||||
} else if (status == UHDK_STATUS_OK) {
|
} else if (status == UHDK_STATUS_OK) {
|
||||||
IShellItem *item = NULL;
|
IShellItem *item = NULL;
|
||||||
files = calloc(1, sizeof(*files));
|
files = profileCalloc(1, sizeof(*files));
|
||||||
if (files && SUCCEEDED(IFileDialog_GetResult(dialog, &item))) {
|
if (files && SUCCEEDED(IFileDialog_GetResult(dialog, &item))) {
|
||||||
files[0] = shellFile(item);
|
files[0] = shellFile(item);
|
||||||
IShellItem_Release(item);
|
IShellItem_Release(item);
|
||||||
|
|
@ -1178,20 +1199,20 @@ static void showDialog(void) {
|
||||||
host.dialog = NULL;
|
host.dialog = NULL;
|
||||||
host.dialog_id = 0;
|
host.dialog_id = 0;
|
||||||
IFileDialog_Release(dialog);
|
IFileDialog_Release(dialog);
|
||||||
free(title);
|
profileFree(title);
|
||||||
free(label);
|
profileFree(label);
|
||||||
free(suggestion);
|
profileFree(suggestion);
|
||||||
if (filters) {
|
if (filters) {
|
||||||
for (uint64_t i = 0; i < d->filters_len; i++) {
|
for (uint64_t i = 0; i < d->filters_len; i++) {
|
||||||
free((void *)filters[i].pszName);
|
profileFree((void *)filters[i].pszName);
|
||||||
free((void *)filters[i].pszSpec);
|
profileFree((void *)filters[i].pszSpec);
|
||||||
}
|
}
|
||||||
free(filters);
|
profileFree(filters);
|
||||||
}
|
}
|
||||||
z_complete(host.app, id, status, &result);
|
z_complete(host.app, id, status, &result);
|
||||||
for (uint64_t i = 0; i < count; i++)
|
for (uint64_t i = 0; i < count; i++)
|
||||||
uhdk_file_release(files[i]);
|
uhdk_file_release(files[i]);
|
||||||
free(files);
|
profileFree(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
static BOOL armWatch(Watch *watch) {
|
static BOOL armWatch(Watch *watch) {
|
||||||
|
|
@ -1207,15 +1228,15 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
|
||||||
WCHAR *path = filePath(root);
|
WCHAR *path = filePath(root);
|
||||||
if (!path)
|
if (!path)
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
Watch *watch = calloc(1, sizeof(*watch));
|
Watch *watch = profileCalloc(1, sizeof(*watch));
|
||||||
if (!watch) {
|
if (!watch) {
|
||||||
free(path);
|
profileFree(path);
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
DWORD attributes = GetFileAttributesW(path);
|
DWORD attributes = GetFileAttributesW(path);
|
||||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||||
free(path);
|
profileFree(path);
|
||||||
free(watch);
|
profileFree(watch);
|
||||||
return osError();
|
return osError();
|
||||||
}
|
}
|
||||||
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||||
|
|
@ -1224,14 +1245,14 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
|
||||||
if (other && (!slash || other > slash))
|
if (other && (!slash || other > slash))
|
||||||
slash = other;
|
slash = other;
|
||||||
if (!slash) {
|
if (!slash) {
|
||||||
free(path);
|
profileFree(path);
|
||||||
free(watch);
|
profileFree(watch);
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
watch->leaf = _wcsdup(slash + 1);
|
watch->leaf = _wcsdup(slash + 1);
|
||||||
if (!watch->leaf) {
|
if (!watch->leaf) {
|
||||||
free(path);
|
profileFree(path);
|
||||||
free(watch);
|
profileFree(watch);
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
slash[1] = 0;
|
slash[1] = 0;
|
||||||
|
|
@ -1239,7 +1260,7 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
|
||||||
watch->directory = CreateFileW(
|
watch->directory = CreateFileW(
|
||||||
path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
||||||
free(path);
|
profileFree(path);
|
||||||
watch->overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
|
watch->overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||||
watch->handle = handle;
|
watch->handle = handle;
|
||||||
if (watch->directory == INVALID_HANDLE_VALUE || !watch->overlapped.hEvent || !armWatch(watch)) {
|
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);
|
CloseHandle(watch->directory);
|
||||||
if (watch->overlapped.hEvent)
|
if (watch->overlapped.hEvent)
|
||||||
CloseHandle(watch->overlapped.hEvent);
|
CloseHandle(watch->overlapped.hEvent);
|
||||||
free(watch->leaf);
|
profileFree(watch->leaf);
|
||||||
free(watch);
|
profileFree(watch);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
watch->next = host.watches;
|
watch->next = host.watches;
|
||||||
|
|
@ -1462,7 +1483,7 @@ void z_native_destroy(uint32_t kind, void *native) {
|
||||||
ReleaseDC(g->window, g->dc);
|
ReleaseDC(g->window, g->dc);
|
||||||
LeaveCriticalSection(&g->lock);
|
LeaveCriticalSection(&g->lock);
|
||||||
DeleteCriticalSection(&g->lock);
|
DeleteCriticalSection(&g->lock);
|
||||||
free(g);
|
profileFree(g);
|
||||||
} else if (kind == Z_KIND_WINDOW) {
|
} else if (kind == Z_KIND_WINDOW) {
|
||||||
Window *w = native;
|
Window *w = native;
|
||||||
Window **link = &host.windows;
|
Window **link = &host.windows;
|
||||||
|
|
@ -1481,7 +1502,7 @@ void z_native_destroy(uint32_t kind, void *native) {
|
||||||
DestroyWindow(w->hwnd);
|
DestroyWindow(w->hwnd);
|
||||||
if (w->menu_source)
|
if (w->menu_source)
|
||||||
uhdk_menu_release(w->menu_source);
|
uhdk_menu_release(w->menu_source);
|
||||||
free(w);
|
profileFree(w);
|
||||||
} else if (kind == Z_KIND_WATCH) {
|
} else if (kind == Z_KIND_WATCH) {
|
||||||
Watch *w = native;
|
Watch *w = native;
|
||||||
Watch **link = &host.watches;
|
Watch **link = &host.watches;
|
||||||
|
|
@ -1496,8 +1517,8 @@ void z_native_destroy(uint32_t kind, void *native) {
|
||||||
}
|
}
|
||||||
CloseHandle(w->directory);
|
CloseHandle(w->directory);
|
||||||
CloseHandle(w->overlapped.hEvent);
|
CloseHandle(w->overlapped.hEvent);
|
||||||
free(w->leaf);
|
profileFree(w->leaf);
|
||||||
free(w);
|
profileFree(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static uhdk_status clipboardWrite(uhdk_text text) {
|
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);
|
size_t length = (wcslen(value) + 1) * sizeof(WCHAR);
|
||||||
HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, length);
|
HGLOBAL memory = GlobalAlloc(GMEM_MOVEABLE, length);
|
||||||
if (!memory) {
|
if (!memory) {
|
||||||
free(value);
|
profileFree(value);
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
void *bytes = GlobalLock(memory);
|
void *bytes = GlobalLock(memory);
|
||||||
if (!bytes) {
|
if (!bytes) {
|
||||||
GlobalFree(memory);
|
GlobalFree(memory);
|
||||||
free(value);
|
profileFree(value);
|
||||||
return UHDK_STATUS_OS_ERROR;
|
return UHDK_STATUS_OS_ERROR;
|
||||||
}
|
}
|
||||||
memcpy(bytes, value, length);
|
memcpy(bytes, value, length);
|
||||||
GlobalUnlock(memory);
|
GlobalUnlock(memory);
|
||||||
free(value);
|
profileFree(value);
|
||||||
if (!OpenClipboard(host.dispatch)) {
|
if (!OpenClipboard(host.dispatch)) {
|
||||||
GlobalFree(memory);
|
GlobalFree(memory);
|
||||||
return UHDK_STATUS_BUSY;
|
return UHDK_STATUS_BUSY;
|
||||||
|
|
@ -1554,13 +1575,13 @@ static uhdk_status clipboardRead(uint64_t id, uint64_t limit) {
|
||||||
if (!text)
|
if (!text)
|
||||||
return UHDK_STATUS_OS_ERROR;
|
return UHDK_STATUS_OS_ERROR;
|
||||||
if (len > limit) {
|
if (len > limit) {
|
||||||
free(text);
|
profileFree(text);
|
||||||
return UHDK_STATUS_BUFFER_TOO_SMALL;
|
return UHDK_STATUS_BUFFER_TOO_SMALL;
|
||||||
}
|
}
|
||||||
uhdk_request_result result = {0};
|
uhdk_request_result result = {0};
|
||||||
result.value.text = (uhdk_text){text, len};
|
result.value.text = (uhdk_text){text, len};
|
||||||
z_complete(host.app, id, UHDK_STATUS_OK, &result);
|
z_complete(host.app, id, UHDK_STATUS_OK, &result);
|
||||||
free(text);
|
profileFree(text);
|
||||||
return Z_DEFERRED;
|
return Z_DEFERRED;
|
||||||
}
|
}
|
||||||
static uhdk_status setIcon(const uhdk_image *image) {
|
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;
|
cmd->target && z_object_kind(cmd->target) == Z_KIND_WINDOW ? windowOf(cmd->target) : NULL;
|
||||||
switch (cmd->op) {
|
switch (cmd->op) {
|
||||||
case Z_WINDOW: {
|
case Z_WINDOW: {
|
||||||
w = calloc(1, sizeof(*w));
|
w = profileCalloc(1, sizeof(*w));
|
||||||
if (!w)
|
if (!w)
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
w->handle = cmd->target;
|
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);
|
AdjustWindowRectExForDpi(&r, style, FALSE, 0, dpi);
|
||||||
WCHAR *title = wide(cmd->window.title);
|
WCHAR *title = wide(cmd->window.title);
|
||||||
if (!title) {
|
if (!title) {
|
||||||
free(w);
|
profileFree(w);
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
HWND parent = cmd->window.parent ? windowOf(cmd->window.parent)->hwnd : NULL;
|
HWND parent = cmd->window.parent ? windowOf(cmd->window.parent)->hwnd : NULL;
|
||||||
HWND hwnd = CreateWindowExW(0, window_class, title, style, CW_USEDEFAULT, CW_USEDEFAULT,
|
HWND hwnd = CreateWindowExW(0, window_class, title, style, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||||
r.right - r.left, r.bottom - r.top, parent, NULL,
|
r.right - r.left, r.bottom - r.top, parent, NULL,
|
||||||
GetModuleHandleW(NULL), w);
|
GetModuleHandleW(NULL), w);
|
||||||
free(title);
|
profileFree(title);
|
||||||
if (!hwnd) {
|
if (!hwnd) {
|
||||||
free(w);
|
profileFree(w);
|
||||||
return osError();
|
return osError();
|
||||||
}
|
}
|
||||||
ImmAssociateContextEx(hwnd, NULL, 0);
|
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)
|
if (!title)
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
BOOL ok = SetWindowTextW(w->hwnd, title);
|
BOOL ok = SetWindowTextW(w->hwnd, title);
|
||||||
free(title);
|
profileFree(title);
|
||||||
return ok ? UHDK_STATUS_OK : osError();
|
return ok ? UHDK_STATUS_OK : osError();
|
||||||
}
|
}
|
||||||
case Z_SIZE: {
|
case Z_SIZE: {
|
||||||
|
|
@ -1831,11 +1852,11 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
|
||||||
if (!url)
|
if (!url)
|
||||||
return UHDK_STATUS_OUT_OF_MEMORY;
|
return UHDK_STATUS_OUT_OF_MEMORY;
|
||||||
if (!UrlIsW(url, URLIS_URL)) {
|
if (!UrlIsW(url, URLIS_URL)) {
|
||||||
free(url);
|
profileFree(url);
|
||||||
return UHDK_STATUS_INVALID_ARGUMENT;
|
return UHDK_STATUS_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
INT_PTR status = (INT_PTR)ShellExecuteW(NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL);
|
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;
|
return status > 32 ? UHDK_STATUS_OK : UHDK_STATUS_OS_ERROR;
|
||||||
}
|
}
|
||||||
case Z_WATCH: {
|
case Z_WATCH: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue