new header parser and generator

This commit is contained in:
Peter Li 2026-09-18 11:29:02 -07:00
parent 97c4b41e12
commit 8a1f8fbd28
20 changed files with 1224 additions and 328 deletions

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
// MIT License
//
// Copyright (c) 2022 Peter Li
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

View File

@ -41,7 +41,7 @@ exe.root_module.addImport("uhdk", uhdk.module("uhdk"));
```
`@import("uhdk")` provides translated public C declarations, including the bundled
OpenGL headers, and links the library and required system frameworks. The
OpenGL and experimental native-handle declarations, and links the library and required system frameworks. The
`uhdk` artifact is also available through `uhdk.artifact("uhdk")` for C consumers
or custom bindings. Set `.shared = true` to build a dynamic library; applications
must arrange its runtime location/rpath.
@ -49,10 +49,24 @@ must arrange its runtime location/rpath.
## C interface
- [uhdk.h](include/uhdk.h): desktop API, lifecycle, ownership, and threading contracts.
- [uhdk_opengl.h](include/uhdk_opengl.h): GLAD 2 OpenGL 3.3 core declarations and per-context loader table.
- [uhdk_native.h](include/uhdk_native.h): optional platform interoperability.
- [OPENGL-SOURCES.md](include/OPENGL-SOURCES.md): GLAD generation recipe and Khronos header provenance and licenses.
All UHDK declarations live in `uhdk.h`. Before its first inclusion, define
`UHDK_ENABLE_OPENGL` to include the bundled GLAD 2 OpenGL 3.3 core declarations
and per-context loader table, and/or `UHDK_ENABLE_NATIVE` for the experimental
native-window handle API:
```c
#define UHDK_ENABLE_OPENGL
#define UHDK_ENABLE_NATIVE
#include "uhdk.h"
```
Presence enables each flag; leave it undefined to disable it. These flags select
header declarations, not library backends. UHDK's own context-management API is
always declared. The supplied Zig module enables both sections. The vendored GL
headers remain unmodified dependencies rather than being pasted into `uhdk.h`.
The public interface uses pointer-and-length text, `char` booleans, and `float`
coordinates. The native bridge is private; consumers do not need Objective-C.
@ -72,9 +86,42 @@ 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.
## Borrowed event payloads
Events and request results carry `const void *data` plus `data_size` instead of
public unions. Check the event type (or successful request kind), then read the
corresponding named payload struct listed in `uhdk.h`. Check `data_size` before
reading fields; unknown types can be ignored. Payload-free events and unsuccessful
request results have NULL data and zero size. Failure diagnostics remain available
in the request envelope.
```c
if (event->type == UHDK_EVENT_KEY &&
event->data_size >= sizeof(uhdk_key_event)) {
const uhdk_key_event *key = (const uhdk_key_event *)event->data;
/* Read key->logical_key, key->pressed, etc. here. */
}
```
One library-owned scratch arena holds the returned batch, its payload structs,
and nested spans. The next valid application-thread poll resets it, even if that
poll is empty or returns an error. Invalid arguments and wrong-thread calls do
not reset it. App destruction also invalidates everything. Rendering does not
advance this lifetime. Copy data you need to keep; copying an envelope alone
copies only its pointers. Retain file/menu references when needed beyond a poll,
but even retained handles cannot outlive the app.
UHDK is pre-release: API and ABI compatibility with earlier revisions is not
guaranteed. Set creation descriptors to `UHDK_ABI_VERSION`. Event envelopes have
a fixed array stride within each returned batch.
The scratch arena is maintained as UHDK-owned code in
[src/arenas.zig](src/arenas.zig), including its supporting storage and tests.
It originated in Ribtech p2; the MIT license and attribution are retained in that file.
## Optional profiling
`uhdk.h` includes `uhdk_profile.h`: scope, frame, thread-name and named allocation/
`uhdk.h` provides 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

View File

@ -27,10 +27,12 @@ pub fn build(b: *std.Build) void {
const unit_run = b.addRunArtifact(unit);
b.step("test", "Run core library tests").dependOn(&unit_run.step);
const public_c = b.addTranslateC(.{
.root_source_file = b.path("include/uhdk_opengl.h"),
.root_source_file = b.path("include/uhdk.h"),
.target = target,
.optimize = optimize,
});
public_c.defineCMacro("UHDK_ENABLE_OPENGL", "1");
public_c.defineCMacro("UHDK_ENABLE_NATIVE", "1");
public_c.addIncludePath(b.path("include"));
if (shared) public_c.defineCMacro("UHDK_SHARED", "1");
const public_module = b.addModule("uhdk", .{

View File

@ -2,7 +2,8 @@
## GLAD 2.0.8
`uhdk_opengl.h` includes the generated `glad/gl.h`: OpenGL 3.3 core, no
`uhdk.h` with `UHDK_ENABLE_OPENGL` includes the generated `glad/gl.h`:
OpenGL 3.3 core, no
extensions, header-only output, explicit per-context tables (`--mx`), and no
platform loader. `src/glad.c` compiles its implementation into libuhdk. Applications
load a `GladGLContext` through their adapter for `uhdk_gl_context_get_proc`.
@ -27,7 +28,7 @@ No generator or Python installation is required to build Uhdk.
## Original Khronos headers
Retained unmodified for consumers choosing their own loader. Do not combine
`GL/glcorearb.h` with `glad/gl.h` or `uhdk_opengl.h` in a translation unit.
`GL/glcorearb.h` with `glad/gl.h` or `UHDK_ENABLE_OPENGL` in a translation unit.
License notices are preserved in each file.
- `include/GL/glcorearb.h`

View File

@ -9,6 +9,13 @@
* <stdint.h> supplies types only; this header requires no libc function calls.
* Native OS frameworks may themselves depend on system runtimes.
*
* Header configuration (define before the first include):
* - UHDK_ENABLE_OPENGL: include bundled GLAD types and per-context loader API.
* - UHDK_ENABLE_NATIVE: declare the experimental borrowed native-handle API.
* - UHDK_PROFILE_BACKEND: quoted consumer instrumentation adapter header.
* Presence enables each feature flag; leave it undefined to disable it.
* These select declarations only, not library backend/build features.
*
* Naming:
* - Types: uhdk_type; functions: uhdk_object_verb; constants: UHDK_CATEGORY_VALUE.
* - create/destroy allocate and dispose unique objects; retain/release manage
@ -27,8 +34,10 @@
* - NULL is allowed for a span's pointer only when its length is zero.
* - Input spans/descriptors are copied before return unless documented otherwise.
* - No allocator ownership crosses the ABI. Release objects through Uhdk.
* - Event spans are borrowed until the next poll or app destruction. Copy spans
* or retain files/menus to keep them. Each poll returns an immutable batch.
* - Event batches and spans are borrowed until the next valid poll on the
* application thread, or app destruction (whichever comes first). Copy spans
* or retain files/menus to keep them. Every such poll resets storage, including empty/error polls.
* Rendering does not reset storage. Each poll returns an immutable batch.
* - create/run/destroy belong to OS main. run invokes one application entry on
* a second thread. All other calls belong to that application thread except
* app_wake, which is thread-safe. Native work is queued to the main thread.
@ -44,11 +53,33 @@
* except documented buffer-size queries.
* - The application owns rendering, document state, file encoding and layout.
* Uhdk provides optional OpenGL 3.3 core context management. Other renderers
* may attach through experimental uhdk_native.h; drawing remains app-owned.
* may attach through the experimental native-handle API; drawing remains app-owned.
*/
#include <stdint.h>
#include "uhdk_profile.h"
/* 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
#ifndef UHDK_API
#if defined(_WIN32) && defined(UHDK_SHARED)
@ -185,7 +216,7 @@ typedef struct uhdk_event_list {
} uhdk_event_list;
#define UHDK_WAIT_FOREVER UINT64_MAX
typedef void(UHDK_CALL *uhdk_app_main_fn)(uhdk_app *, void *user);
typedef void(UHDK_CALL *uhdk_app_main_fn)(uhdk_app *app, void *user);
typedef struct uhdk_app_desc {
uint32_t struct_size;
uint32_t abi_version;
@ -196,27 +227,34 @@ typedef struct uhdk_app_desc {
} uhdk_app_desc;
UHDK_API uint32_t UHDK_CALL uhdk_get_abi_version(void);
UHDK_API uhdk_status UHDK_CALL uhdk_app_create(const uhdk_app_desc *, uhdk_app **out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_run(uhdk_app *, uhdk_app_main_fn, void *user); /* Once. */
UHDK_API uhdk_status UHDK_CALL uhdk_app_create(const uhdk_app_desc *desc, uhdk_app **out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_run(
uhdk_app *app,
uhdk_app_main_fn main_fn,
void *user
); /* Once. */
/* Zero timeout does not wait; finite timeouts use monotonic nanoseconds.
* STOPPED is delivered once; later polls return UNAVAILABLE. */
UHDK_API uhdk_status UHDK_CALL uhdk_app_poll_events(uhdk_app *, uint64_t timeout_ns,
uhdk_event_list *out);
UHDK_API void UHDK_CALL uhdk_app_quit(uhdk_app *); /* Accept/request shutdown. */
UHDK_API void UHDK_CALL uhdk_app_wake(uhdk_app *); /* Thread-safe, coalesced WAKE event. */
UHDK_API uhdk_status UHDK_CALL uhdk_app_poll_events(
uhdk_app *app,
uint64_t timeout_ns,
uhdk_event_list *out
);
UHDK_API void UHDK_CALL uhdk_app_quit(uhdk_app *app); /* Accept/request shutdown. */
UHDK_API void UHDK_CALL uhdk_app_wake(uhdk_app *app); /* Thread-safe, coalesced WAKE event. */
/* Monotonic nanoseconds since app creation, in the same clock as event.time_ns. */
UHDK_API uint64_t UHDK_CALL uhdk_app_get_time_ns(uhdk_app *);
UHDK_API uint64_t UHDK_CALL uhdk_app_get_time_ns(uhdk_app *app);
/* One pending deadline per app. Replaces any previous deadline; 0 cancels it.
* A past deadline queues WAKE at the next dispatch. On expiry the deadline is
* cleared before delivering WAKE. Explicit wakes may coalesce with expiry but
* do not cancel a future deadline. No event is delivered inline. App code maintains
* multiple timers by scheduling its earliest deadline. No real-time guarantee.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_wake_deadline(uhdk_app *, uint64_t deadline_ns);
UHDK_API void UHDK_CALL uhdk_app_destroy(uhdk_app *); /* After run; releases child objects. */
UHDK_API uhdk_backend UHDK_CALL uhdk_app_get_backend(uhdk_app *);
UHDK_API uhdk_bool UHDK_CALL uhdk_app_has_capability(uhdk_app *, uhdk_capability);
UHDK_API uhdk_status UHDK_CALL uhdk_request_cancel(uhdk_app *, uhdk_request_id);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_wake_deadline(uhdk_app *app, uint64_t deadline_ns);
UHDK_API void UHDK_CALL uhdk_app_destroy(uhdk_app *app); /* After run; releases child objects. */
UHDK_API uhdk_backend UHDK_CALL uhdk_app_get_backend(uhdk_app *app);
UHDK_API uhdk_bool UHDK_CALL uhdk_app_has_capability(uhdk_app *app, uhdk_capability capability);
UHDK_API uhdk_status UHDK_CALL uhdk_request_cancel(uhdk_app *app, uhdk_request_id request_id);
/* Application-thread CPU execution time in nanoseconds, not elapsed wall time.
* Bracket user update/input processing with begin_tick/end_tick, AFTER polling
@ -250,9 +288,9 @@ typedef struct uhdk_app_stats {
float frames_per_second;
} uhdk_app_stats;
UHDK_API uhdk_status UHDK_CALL uhdk_app_begin_tick(uhdk_app *);
UHDK_API uhdk_status UHDK_CALL uhdk_app_end_tick(uhdk_app *);
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_stats(uhdk_app *, uhdk_app_stats *out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_begin_tick(uhdk_app *app);
UHDK_API uhdk_status UHDK_CALL uhdk_app_end_tick(uhdk_app *app);
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_stats(uhdk_app *app, uhdk_app_stats *out);
/* Logical coordinates: top-left origin, x right, y down. Window geometry uses
* content-area units. Framebuffer dimensions are pixels; use reported sizes,
@ -294,25 +332,46 @@ typedef struct uhdk_window_metrics {
uhdk_bool visible;
} uhdk_window_metrics;
UHDK_API uhdk_status UHDK_CALL uhdk_window_create_async(uhdk_app *, const uhdk_window_desc *,
uhdk_request_id *out);
UHDK_API void UHDK_CALL uhdk_window_destroy(uhdk_window *);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_title_async(uhdk_window *, uhdk_text,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_size_async(uhdk_window *, uhdk_size,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_state_async(uhdk_window *, uhdk_window_state,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_visible_async(uhdk_window *, uhdk_bool,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL
uhdk_window_request_focus_async(uhdk_window *, uhdk_request_id *out); /* OS may decline. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_metrics(uhdk_window *, uhdk_window_metrics *out);
UHDK_API void UHDK_CALL uhdk_window_invalidate(uhdk_window *); /* Coalesced redraw event. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_create_async(
uhdk_app *app,
const uhdk_window_desc *desc,
uhdk_request_id *out
);
UHDK_API void UHDK_CALL uhdk_window_destroy(uhdk_window *window);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_title_async(
uhdk_window *window,
uhdk_text title,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_size_async(
uhdk_window *window,
uhdk_size size,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_state_async(
uhdk_window *window,
uhdk_window_state state,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_visible_async(
uhdk_window *window,
uhdk_bool visible,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_request_focus_async(
uhdk_window *window,
uhdk_request_id *out
); /* OS may decline. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_metrics(
uhdk_window *window,
uhdk_window_metrics *out
);
UHDK_API void UHDK_CALL uhdk_window_invalidate(uhdk_window *window); /* Coalesced redraw event. */
/* Optional OpenGL context management (application/render thread).
* Include <uhdk_opengl.h> for bundled Khronos GL types, constants and PFNGL*
* function-pointer types. This header alone does not introduce GL names.
* Define UHDK_ENABLE_OPENGL before including this header for bundled Khronos
* GL types, constants, and PFNGL* function-pointer types. Without that flag,
* no GL names are introduced.
*
* Creation requires UHDK_WINDOW_OPENGL and guarantees desktop OpenGL >= 3.3,
* core profile, with GLSL >= 3.30. macOS selects its 4.1 core profile because
@ -341,28 +400,39 @@ UHDK_API void UHDK_CALL uhdk_window_invalidate(uhdk_window *); /* Coalesced redr
* type before calling. GL uses stdcall on Win32; Uhdk exports still use C ABI. */
typedef void(UHDK_GL_CALL *uhdk_gl_proc)(void);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_create_async(uhdk_window *, uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_begin_frame(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_end_frame(uhdk_gl_context *);
UHDK_API void UHDK_CALL uhdk_gl_context_destroy(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_make_current(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_clear_current(uhdk_app *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_create_async(
uhdk_window *window,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_begin_frame(uhdk_gl_context *context);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_end_frame(uhdk_gl_context *context);
UHDK_API void UHDK_CALL uhdk_gl_context_destroy(uhdk_gl_context *context);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_make_current(uhdk_gl_context *context);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_clear_current(uhdk_app *app);
/* Actual negotiated version, not the requested minimum. */
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_version(uhdk_gl_context *, uint32_t *out_major,
uint32_t *out_minor);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_swap_buffers(uhdk_gl_context *);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_version(
uhdk_gl_context *context,
uint32_t *out_major,
uint32_t *out_minor
);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_swap_buffers(uhdk_gl_context *context);
/* Explicitly set 0 (unsynchronized) or 1 (vsync); other values are invalid.
* No initial interval is guaranteed. Unsupported control returns UNSUPPORTED;
* compositor/driver policy may override presentation timing. */
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_set_swap_interval(uhdk_gl_context *,
int32_t interval);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_set_swap_interval(
uhdk_gl_context *context,
int32_t interval
);
/* Requires this context current. Accepts pointer+length ASCII GL symbol names.
* Missing symbols return NOT_FOUND. Pointers are valid until context destruction
* and must only be used with that context current. A non-NULL pointer alone does
* not prove extension/version support; check GL version/extension availability.
* Loads both platform-library exports and driver-provided GL entry points. */
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_proc(uhdk_gl_context *, uhdk_text name,
uhdk_gl_proc *out);
UHDK_API uhdk_status UHDK_CALL uhdk_gl_context_get_proc(
uhdk_gl_context *context,
uhdk_text name,
uhdk_gl_proc *out
);
/* Keyboard events are not text. Physical key = USB HID keyboard-page usage;
* 0 means unknown. Logical key = Unicode scalar, or UHDK_KEY_* below.
@ -448,12 +518,18 @@ enum {
UHDK_POINTER_NORMAL = 0,
UHDK_POINTER_CAPTURE = 1
};
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_cursor_shape_async(uhdk_window *, uhdk_cursor_shape,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_cursor_shape_async(
uhdk_window *window,
uhdk_cursor_shape shape,
uhdk_request_id *out
);
/* Capture delivers selection-drag events outside the window. Focus loss or OS
* cancellation restores NORMAL and emits POINTER_MODE_CHANGED. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_pointer_mode_async(uhdk_window *, uhdk_pointer_mode,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_pointer_mode_async(
uhdk_window *window,
uhdk_pointer_mode mode,
uhdk_request_id *out
);
/* Text offsets/lengths are UTF-8 byte counts, always at scalar boundaries.
* surrounding_text is a bounded snapshot; document_offset locates it in the
@ -476,11 +552,19 @@ typedef struct uhdk_text_state {
uint64_t selection_len;
uhdk_rect caret; /* Window-local logical coordinates. */
} uhdk_text_state;
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_input_async(uhdk_window *, uhdk_bool enabled,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_state(uhdk_window *, const uhdk_text_state *);
UHDK_API uhdk_status UHDK_CALL uhdk_window_cancel_composition_async(uhdk_window *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_input_async(
uhdk_window *window,
uhdk_bool enabled,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_text_state(
uhdk_window *window,
const uhdk_text_state *state
);
UHDK_API uhdk_status UHDK_CALL uhdk_window_cancel_composition_async(
uhdk_window *window,
uhdk_request_id *out
);
/* File references preserve native names, including non-UTF-8 POSIX paths.
* Display names are UTF-8. URI/native-path export is explicit and owned by the
@ -500,15 +584,25 @@ enum {
UHDK_FILE_URI = 1,
UHDK_FILE_NATIVE_PATH = 2
};
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_native_path(uhdk_app *, uhdk_bytes path,
uhdk_file **out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_uri(uhdk_app *, uhdk_text uri,
uhdk_file **out);
UHDK_API void UHDK_CALL uhdk_file_retain(uhdk_file *);
UHDK_API void UHDK_CALL uhdk_file_release(uhdk_file *);
UHDK_API uhdk_status UHDK_CALL uhdk_file_copy_representation(uhdk_file *, uhdk_file_representation,
uint8_t *buffer, uint64_t capacity,
uint64_t *out_len);
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_native_path(
uhdk_app *app,
uhdk_bytes path,
uhdk_file **out
);
UHDK_API uhdk_status UHDK_CALL uhdk_file_create_from_uri(
uhdk_app *app,
uhdk_text uri,
uhdk_file **out
);
UHDK_API void UHDK_CALL uhdk_file_retain(uhdk_file *file);
UHDK_API void UHDK_CALL uhdk_file_release(uhdk_file *file);
UHDK_API uhdk_status UHDK_CALL uhdk_file_copy_representation(
uhdk_file *file,
uhdk_file_representation representation,
uint8_t *buffer,
uint64_t capacity,
uint64_t *out_len
);
typedef uint32_t uhdk_file_type;
enum {
@ -534,11 +628,17 @@ typedef struct uhdk_file_info {
* New POSIX files use mode 0600. No power-loss durability or conflict detection promise.
* No silent truncate fallback. Cancellation before rename leaves the target intact.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_file_read_all_async(uhdk_file *, uint64_t max_bytes,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_write_all_async(uhdk_file *, uhdk_bytes,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_get_info_async(uhdk_file *, uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_read_all_async(
uhdk_file *file,
uint64_t max_bytes,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_file_write_all_async(
uhdk_file *file,
uhdk_bytes bytes,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_file_get_info_async(uhdk_file *file, uhdk_request_id *out);
typedef uint32_t uhdk_dialog_kind;
enum {
@ -564,29 +664,40 @@ typedef struct uhdk_file_dialog_desc {
const uhdk_file_filter *filters;
uint64_t filters_len;
} uhdk_file_dialog_desc;
UHDK_API uhdk_status UHDK_CALL uhdk_file_dialog_show_async(uhdk_app *,
const uhdk_file_dialog_desc *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_file_dialog_show_async(
uhdk_app *app,
const uhdk_file_dialog_desc *desc,
uhdk_request_id *out
);
/* Plain-text clipboard only. Writes copy UTF-8 before returning; empty text is
* valid. Reads negotiate native text formats internally and complete with text
* borrowed until the next event poll. NOT_FOUND means no text format.
* borrowed for the event batch lifetime. NOT_FOUND means no text format.
* max_bytes bounds the UTF-8 result; excess returns BUFFER_TOO_SMALL, never a
* truncated string. A read remains async even when a backend can read instantly.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_write_text_async(uhdk_app *, uhdk_text,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_read_text_async(uhdk_app *, uint64_t max_bytes,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_write_text_async(
uhdk_app *app,
uhdk_text text,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_clipboard_read_text_async(
uhdk_app *app,
uint64_t max_bytes,
uhdk_request_id *out
);
/* Incoming file drops only. When enabled, Uhdk negotiates copy-only file drops
* and emits DROP_FILES with borrowed file references plus the drop position.
* Retain references to use after the next poll. Directory references are allowed;
* Retain references to use beyond the event batch lifetime. Directory references are allowed;
* the application decides whether to open them. No source deletion, outgoing
* drag, data offers, or application-visible drop-completion transaction.
*/
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_drop_enabled_async(uhdk_window *, uhdk_bool,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_drop_enabled_async(
uhdk_window *window,
uhdk_bool enabled,
uhdk_request_id *out
);
/* Menus are immutable snapshots. Command IDs are application-assigned, nonzero
* and unique within a menu tree. Shortcuts use logical keys + modifiers.
@ -623,26 +734,49 @@ typedef struct uhdk_menu_item {
uhdk_menu *submenu; /* Retained by the created parent menu. */
} uhdk_menu_item;
UHDK_API uhdk_status UHDK_CALL uhdk_menu_create(uhdk_app *, const uhdk_menu_item *, uint64_t len,
uhdk_menu **out);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_set_item_flags_async(uhdk_menu *, uint64_t command_id,
uhdk_menu_flags,
uhdk_request_id *out);
UHDK_API void UHDK_CALL uhdk_menu_retain(uhdk_menu *);
UHDK_API void UHDK_CALL uhdk_menu_release(uhdk_menu *);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_menu_async(uhdk_app *, uhdk_menu *,
uhdk_request_id *out); /* NULL clears. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_menu_async(uhdk_window *, uhdk_menu *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_popup_async(uhdk_menu *, uhdk_window *, uhdk_point,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_icon_async(uhdk_app *, const uhdk_image *,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_create(
uhdk_app *app,
const uhdk_menu_item *items,
uint64_t len,
uhdk_menu **out
);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_set_item_flags_async(
uhdk_menu *menu,
uint64_t command_id,
uhdk_menu_flags flags,
uhdk_request_id *out
);
UHDK_API void UHDK_CALL uhdk_menu_retain(uhdk_menu *menu);
UHDK_API void UHDK_CALL uhdk_menu_release(uhdk_menu *menu);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_menu_async(
uhdk_app *app,
uhdk_menu *menu,
uhdk_request_id *out
); /* NULL clears. */
UHDK_API uhdk_status UHDK_CALL uhdk_window_set_menu_async(
uhdk_window *window,
uhdk_menu *menu,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_menu_popup_async(
uhdk_menu *menu,
uhdk_window *window,
uhdk_point position,
uhdk_request_id *out
);
UHDK_API uhdk_status UHDK_CALL uhdk_app_set_icon_async(
uhdk_app *app,
const uhdk_image *image,
uhdk_request_id *out
);
/* Successful shell completion means handed off to the OS, not that a target
* application finished loading. URLs are not shell command strings. */
UHDK_API uhdk_status UHDK_CALL uhdk_shell_open_url_async(uhdk_app *, uhdk_text url,
uhdk_request_id *out);
UHDK_API uhdk_status UHDK_CALL uhdk_shell_open_url_async(
uhdk_app *app,
uhdk_text url,
uhdk_request_id *out
);
typedef uint32_t uhdk_change_flags;
enum {
@ -663,7 +797,7 @@ enum {
* as remove/create; no rename pairing is promised. RESCAN_REQUIRED means inspect
* current state. Pending notifications are suppressed after destroy; already borrowed batches remain valid. */
UHDK_API uhdk_status UHDK_CALL uhdk_watch_create_async(uhdk_file *root, uhdk_request_id *out);
UHDK_API void UHDK_CALL uhdk_watch_destroy(uhdk_watch *);
UHDK_API void UHDK_CALL uhdk_watch_destroy(uhdk_watch *watch);
typedef uint32_t uhdk_appearance;
enum {
@ -678,7 +812,7 @@ typedef struct uhdk_preferences {
uhdk_bool reduced_motion;
uhdk_bool high_contrast;
} uhdk_preferences;
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_preferences(uhdk_app *, uhdk_preferences *out);
UHDK_API uhdk_status UHDK_CALL uhdk_app_get_preferences(uhdk_app *app, uhdk_preferences *out);
/* Event payloads. Window is NULL for app-wide events. time_ns is monotonic time
* relative to app creation, not wall time. Do not persist event pointers.
@ -753,58 +887,39 @@ enum {
UHDK_EDIT_TAB
};
typedef struct uhdk_request_result {
uhdk_request_id id;
uhdk_request_kind kind;
uhdk_status status;
uhdk_text diagnostic; /* Optional; for humans, never parse for logic. */
union {
uhdk_file_list files; /* FILE_DIALOG */
uhdk_text text; /* CLIPBOARD_TEXT */
uhdk_window *window;
uhdk_gl_context *gl_context;
uhdk_watch *watch;
uhdk_bytes bytes;
uhdk_file_info info;
} value; /* Only valid on UHDK_STATUS_OK; SHELL/COMMAND/FILE_WRITE/MENU_POPUP have no payload. */
} uhdk_request_result;
struct uhdk_event {
uint32_t struct_size;
uhdk_event_type type;
uint64_t time_ns;
uint64_t input_id; /* Correlates raw key and native edit/text; zero for non-input. */
uhdk_window *window;
union {
uhdk_bool active;
uhdk_file_list files;
uhdk_window_metrics metrics;
uhdk_preferences preferences;
struct {
/* Event data is a pointer to the named payload type selected by type.
* All payloads are immutable and naturally aligned. data_size is the payload
* size in bytes; check it before accessing fields introduced by newer versions.
* Payload-free events have NULL data and data_size == 0. Unknown types may be
* ignored. Event envelopes have a fixed stride within each returned batch.
*/
typedef struct uhdk_key_event {
uint32_t physical_key;
uhdk_key logical_key;
uhdk_modifier_flags modifiers;
uhdk_bool pressed;
uhdk_bool repeat;
} key;
struct {
} uhdk_key_event;
typedef struct uhdk_pointer_event {
uhdk_point position;
uhdk_point delta; /* delta uses logical units. */
uhdk_pointer_button button;
uhdk_modifier_flags modifiers;
uhdk_bool pressed;
uint32_t click_count;
} pointer;
uhdk_pointer_mode pointer_mode;
struct {
} uhdk_pointer_event;
typedef struct uhdk_scroll_event {
uhdk_point position;
uhdk_point delta; /* Positive scroll = toward right/bottom. */
uhdk_scroll_unit unit;
uhdk_scroll_phase phase;
uhdk_modifier_flags modifiers;
uhdk_bool momentum;
} scroll;
struct {
} uhdk_scroll_event;
typedef struct uhdk_text_event {
uhdk_text text;
uint64_t revision;
uhdk_bool has_replacement;
@ -813,26 +928,78 @@ struct uhdk_event {
uint64_t replacement_len;
uint64_t selection_start;
uint64_t selection_len; /* Within preedit text. */
} text;
struct {
} uhdk_text_event;
typedef struct uhdk_edit_event {
uhdk_edit_command command;
uhdk_bool extend_selection;
} edit;
struct {
} uhdk_edit_event;
typedef struct uhdk_drop_files_event {
uhdk_file_list files;
uhdk_point position;
} drop_files;
struct {
} uhdk_drop_files_event;
typedef struct uhdk_menu_event {
uint64_t command_id;
} menu;
struct {
} uhdk_menu_event;
typedef struct uhdk_watch_event {
uhdk_watch *watch;
uhdk_change_flags flags;
uhdk_status status; /* Non-OK: watch failure; rescan/recreate as needed. */
uhdk_file *file; /* Nullable when only the watched root is known. */
} watch;
uhdk_request_result request;
} data;
} uhdk_watch_event;
typedef struct uhdk_activation_event {
uhdk_bool active;
} uhdk_activation_event;
typedef struct uhdk_pointer_mode_event {
uhdk_pointer_mode mode;
} uhdk_pointer_mode_event;
typedef struct uhdk_window_result {
uhdk_window *window;
} uhdk_window_result;
typedef struct uhdk_gl_context_result {
uhdk_gl_context *gl_context;
} uhdk_gl_context_result;
typedef struct uhdk_watch_result {
uhdk_watch *watch;
} uhdk_watch_result;
/* Request data mapping (only valid on UHDK_STATUS_OK):
* FILE_DIALOG: uhdk_file_list; CLIPBOARD_TEXT: uhdk_text;
* WINDOW: uhdk_window_result; GL_CONTEXT: uhdk_gl_context_result;
* WATCH: uhdk_watch_result; FILE_READ: uhdk_bytes; FILE_INFO: uhdk_file_info.
* SHELL, COMMAND, FILE_WRITE, MENU_POPUP and failures have NULL/0 data.
*/
typedef struct uhdk_request_result {
uhdk_request_id id;
uhdk_request_kind kind;
uhdk_status status;
uhdk_text diagnostic; /* Optional; for humans, never parse for logic. */
const void *data;
uint64_t data_size;
} uhdk_request_result;
/* Event data mapping:
* APP_ACTIVATION: uhdk_activation_event; OPEN_FILES: uhdk_file_list;
* WINDOW_METRICS: uhdk_window_metrics; PREFERENCES_CHANGED: uhdk_preferences;
* KEY: uhdk_key_event; POINTER_ENTER/LEAVE/MOVE/BUTTON: uhdk_pointer_event;
* POINTER_MODE_CHANGED: uhdk_pointer_mode_event; SCROLL: uhdk_scroll_event;
* TEXT_COMMIT/PREEDIT: uhdk_text_event; EDIT_COMMAND: uhdk_edit_event;
* DROP_FILES: uhdk_drop_files_event; MENU_COMMAND: uhdk_menu_event;
* WATCH: uhdk_watch_event; REQUEST_DONE: uhdk_request_result.
* All other events have NULL/0 data.
*/
struct uhdk_event {
uint32_t struct_size;
uhdk_event_type type;
uint64_t time_ns;
uint64_t input_id; /* Correlates raw key and native edit/text; zero for non-input. */
uhdk_window *window;
const void *data;
uint64_t data_size;
};
/* Lifecycle policy: close/quit requests are advisory. Destroy the window or call
@ -852,7 +1019,43 @@ struct uhdk_event {
* semantic trees, notifications, persistent access bookmarks and streaming data.
*/
#ifdef UHDK_ENABLE_NATIVE
/* Experimental platform escape hatch; native handles may require OS headers. */
/* Platform escape hatch. Returned handles are borrowed and must not be released.
* macOS: primary=NSWindow*, secondary=NSView*; Windows: primary=HWND.
* Wayland: primary=wl_display*, secondary=wl_surface*.
* X11: primary=Display*, value=XID. Lifetime ends with the Uhdk window.
* With UHDK_WINDOW_OPENGL, Uhdk owns drawable setup; do not replace its native
* view/surface or pixel format. Otherwise the external renderer owns setup.
* Borrowing a handle does not change platform threading rules: AppKit window
* and view operations still belong on the macOS main thread.
*/
typedef struct uhdk_native_window {
uint32_t struct_size;
uhdk_backend backend;
void *primary;
void *secondary;
uint64_t value;
} uhdk_native_window;
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_native(uhdk_window *window, uhdk_native_window *out);
#endif /* UHDK_ENABLE_NATIVE */
#ifdef __cplusplus
} /* extern "C" */
#endif
#ifdef UHDK_ENABLE_OPENGL
/* GLAD 2: OpenGL 3.3 core, no extensions, explicit per-context function table.
* Do not combine with platform GL headers or GL/glcorearb.h.
* The loader implementation is linked into libuhdk; consumers must not define
* GLAD_GL_IMPLEMENTATION when linking that library.
* Load a GladGLContext via gladLoadGLContextUserPtr and an adapter calling
* uhdk_gl_context_get_proc, inside a begin_frame/end_frame bracket. Keep one
* table per context and use it only while that context is current.
*/
#if defined(UHDK_SHARED)
#define GLAD_API_CALL_EXPORT
#endif
#include "glad/gl.h"
#endif /* UHDK_ENABLE_OPENGL */
#endif /* UHDK_H_INCLUDED */

View File

@ -1,33 +0,0 @@
#ifndef UHDK_NATIVE_H_INCLUDED
#define UHDK_NATIVE_H_INCLUDED
/* Optional experimental platform escape hatch; outside the v0.1 core contract.
* Native handles may require OS headers. Not needed for Uhdk OpenGL rendering.
*/
#include "uhdk.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Platform escape hatch. Returned handles are borrowed and must not be released.
* macOS: primary=NSWindow*, secondary=NSView*; Windows: primary=HWND.
* Wayland: primary=wl_display*, secondary=wl_surface*.
* X11: primary=Display*, value=XID. Lifetime ends with the Uhdk window.
* With UHDK_WINDOW_OPENGL, Uhdk owns drawable setup; do not replace its native
* view/surface or pixel format. Otherwise the external renderer owns setup.
* Borrowing a handle does not change platform threading rules: AppKit window
* and view operations still belong on the macOS main thread.
*/
typedef struct uhdk_native_window {
uint32_t struct_size;
uhdk_backend backend;
void *primary;
void *secondary;
uint64_t value;
} uhdk_native_window;
UHDK_API uhdk_status UHDK_CALL uhdk_window_get_native(uhdk_window *, uhdk_native_window *out);
#ifdef __cplusplus
}
#endif
#endif /* UHDK_NATIVE_H_INCLUDED */

View File

@ -1,19 +0,0 @@
#ifndef UHDK_OPENGL_H_INCLUDED
#define UHDK_OPENGL_H_INCLUDED
#include "uhdk.h"
/* GLAD 2: OpenGL 3.3 core, no extensions, explicit per-context function table.
* Do not combine with platform GL headers or GL/glcorearb.h.
* The loader implementation is linked into libuhdk; consumers must not define
* GLAD_GL_IMPLEMENTATION when linking that library.
* Load a GladGLContext via gladLoadGLContextUserPtr and an adapter calling
* uhdk_gl_context_get_proc, inside a begin_frame/end_frame bracket. Keep one
* table per context and use it only while that context is current.
*/
#if defined(UHDK_SHARED)
#define GLAD_API_CALL_EXPORT
#endif
#include "glad/gl.h"
#endif /* UHDK_OPENGL_H_INCLUDED */

View File

@ -1,28 +0,0 @@
#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

View File

@ -6,6 +6,7 @@ const abi = @import("abi.zig");
const Event = @import("Event.zig");
const Request = @import("Request.zig");
const Resource = @import("Resource.zig");
const arenas = @import("arenas.zig");
const Application = @This();
gpa: std.mem.Allocator,
@ -24,7 +25,7 @@ io_tail: ?*Request = null,
pending_head: ?*Event = null,
pending_tail: ?*Event = null,
published: ?*Event = null,
events: std.ArrayList(c.uhdk_event) = .empty,
scratch: arenas.BumpArena,
name: []u8,
epoch: u64,
deadline: u64 = 0,
@ -47,7 +48,9 @@ pub fn create(gpa: std.mem.Allocator, io: std.Io, name: []const u8) !*Applicatio
const self = try gpa.create(Application);
errdefer gpa.destroy(self);
const owned_name = try gpa.dupe(u8, name);
self.* = .{ .gpa = gpa, .io = io, .name = owned_name, .epoch = runtime.now(io) };
errdefer gpa.free(owned_name);
const scratch = try arenas.BumpArena.init(gpa, io);
self.* = .{ .gpa = gpa, .io = io, .name = owned_name, .scratch = scratch, .epoch = runtime.now(io) };
return self;
}
pub fn lock(self: *Application) void {

View File

@ -8,5 +8,5 @@ const Event = @This();
next: ?*Event = null,
arena: abi.Arena,
gpa: std.mem.Allocator,
event: c.uhdk_event = std.mem.zeroes(c.uhdk_event),
event: c.z_event = std.mem.zeroes(c.z_event),
pins: std.ArrayList(*Resource) = .empty,

60
src/EventPayload.zig Normal file
View File

@ -0,0 +1,60 @@
//! Convert private producer unions to immutable, arena-owned public payloads.
const std = @import("std");
const c = @import("native");
const abi = @import("abi.zig");
const Data = struct { ptr: ?*const anyopaque = null, size: u64 = 0 };
fn store(comptime T: type, allocator: std.mem.Allocator, value: T) !Data {
const ptr = try allocator.create(T);
ptr.* = value;
return .{ .ptr = ptr, .size = @sizeOf(T) };
}
fn files(allocator: std.mem.Allocator, value: c.uhdk_file_list) !c.uhdk_file_list {
if (value.len == 0) return .{ .ptr = null, .len = 0 };
const ptr = try allocator.dupe(?*c.uhdk_file, value.ptr[0..@intCast(value.len)]);
return .{ .ptr = ptr.ptr, .len = ptr.len };
}
fn result(allocator: std.mem.Allocator, source: c.z_request_result) !c.uhdk_request_result {
const data: Data = if (source.status != abi.OK) .{} else switch (source.kind) {
c.UHDK_REQUEST_FILE_DIALOG => try store(c.uhdk_file_list, allocator, try files(allocator, source.value.files)),
c.UHDK_REQUEST_CLIPBOARD_TEXT => try store(c.uhdk_text, allocator, try abi.copyText(allocator, source.value.text)),
c.UHDK_REQUEST_FILE_READ => try store(c.uhdk_bytes, allocator, try abi.copyBytes(allocator, source.value.bytes)),
c.UHDK_REQUEST_FILE_INFO => try store(c.uhdk_file_info, allocator, source.value.info),
c.UHDK_REQUEST_WINDOW => try store(c.uhdk_window_result, allocator, .{ .window = source.value.window }),
c.UHDK_REQUEST_GL_CONTEXT => try store(c.uhdk_gl_context_result, allocator, .{ .gl_context = source.value.gl_context }),
c.UHDK_REQUEST_WATCH => try store(c.uhdk_watch_result, allocator, .{ .watch = source.value.watch }),
else => .{},
};
return .{ .id = source.id, .kind = source.kind, .status = source.status, .diagnostic = try abi.copyText(allocator, source.diagnostic), .data = data.ptr, .data_size = data.size };
}
pub fn pack(allocator: std.mem.Allocator, source: c.z_event) !c.uhdk_event {
const data: Data = switch (source.type) {
c.UHDK_EVENT_APP_ACTIVATION => try store(c.uhdk_activation_event, allocator, .{ .active = source.data.active }),
c.UHDK_EVENT_OPEN_FILES => try store(c.uhdk_file_list, allocator, try files(allocator, source.data.files)),
c.UHDK_EVENT_WINDOW_METRICS => try store(c.uhdk_window_metrics, allocator, source.data.metrics),
c.UHDK_EVENT_PREFERENCES_CHANGED => try store(c.uhdk_preferences, allocator, source.data.preferences),
c.UHDK_EVENT_KEY => try store(c.uhdk_key_event, allocator, source.data.key),
c.UHDK_EVENT_POINTER_ENTER, c.UHDK_EVENT_POINTER_LEAVE, c.UHDK_EVENT_POINTER_MOVE, c.UHDK_EVENT_POINTER_BUTTON => try store(c.uhdk_pointer_event, allocator, source.data.pointer),
c.UHDK_EVENT_POINTER_MODE_CHANGED => try store(c.uhdk_pointer_mode_event, allocator, .{ .mode = source.data.pointer_mode }),
c.UHDK_EVENT_SCROLL => try store(c.uhdk_scroll_event, allocator, source.data.scroll),
c.UHDK_EVENT_TEXT_COMMIT, c.UHDK_EVENT_TEXT_PREEDIT => data: {
var text = source.data.text;
text.text = try abi.copyText(allocator, text.text);
break :data try store(c.uhdk_text_event, allocator, text);
},
c.UHDK_EVENT_EDIT_COMMAND => try store(c.uhdk_edit_event, allocator, source.data.edit),
c.UHDK_EVENT_DROP_FILES => data: {
var drop = source.data.drop_files;
drop.files = try files(allocator, drop.files);
break :data try store(c.uhdk_drop_files_event, allocator, drop);
},
c.UHDK_EVENT_MENU_COMMAND => try store(c.uhdk_menu_event, allocator, source.data.menu),
c.UHDK_EVENT_WATCH => try store(c.uhdk_watch_event, allocator, source.data.watch),
c.UHDK_EVENT_REQUEST_DONE => try store(c.uhdk_request_result, allocator, try result(allocator, source.data.request)),
else => .{},
};
return .{ .struct_size = @sizeOf(c.uhdk_event), .type = source.type, .time_ns = source.time_ns, .input_id = source.input_id, .window = source.window, .data = data.ptr, .data_size = data.size };
}

View File

@ -100,7 +100,7 @@ pub export fn uhdk_app_poll_events(
defer poll_zone.end();
runtime.destroyEvents(application.published);
application.published = null;
application.events.clearRetainingCapacity();
application.scratch.reset();
out.* = .{ .ptr = null, .len = 0 };
application.lock();
defer application.unlock();
@ -151,19 +151,30 @@ pub export fn uhdk_app_poll_events(
while (cursor) |node| : (cursor = node.next) {
count += 1;
}
application.events.ensureTotalCapacity(application.gpa, count) catch return abi.OOM;
const allocator = application.scratch.allocator();
const events = allocator.alloc(c.uhdk_event, count) catch return abi.OOM;
var len: usize = 0;
cursor = application.pending_head;
// Do not consume nodes until the entire batch has been converted. On OOM,
// the queue (and all terminal request completions) remains retryable.
while (cursor) |node| : (cursor = node.next) {
const event = node.event;
if (event.window != null and !runtime.fromObjectHandle(event.window).alive) continue;
if (event.type == c.UHDK_EVENT_WATCH and !runtime.fromObjectHandle(event.data.watch.watch).alive) continue;
events[len] = @import("../EventPayload.zig").pack(allocator, event) catch return abi.OOM;
len += 1;
}
application.published = application.pending_head;
application.pending_head = null;
application.pending_tail = null;
cursor = application.published;
while (cursor) |node| : (cursor = node.next) {
const event = node.event;
if (event.window != null and !runtime.fromObjectHandle(event.window).alive) continue;
if (event.type == c.UHDK_EVENT_WATCH and !runtime.fromObjectHandle(event.data.watch.watch).alive) continue;
application.events.appendAssumeCapacity(event);
if (event.type == c.UHDK_EVENT_STOPPED) application.stopped_seen = true;
// Payload bytes now live in scratch. Keep only resource pins and nodes
// until the next poll; their private producer spans are no longer read.
_ = node.arena.reset(.free_all);
if (node.event.type == c.UHDK_EVENT_STOPPED) application.stopped_seen = true;
}
out.* = .{ .ptr = application.events.items.ptr, .len = application.events.items.len };
out.* = .{ .ptr = if (len == 0) null else events.ptr, .len = len };
application.stats.event_count +|= out.*.len;
return abi.OK;
}
@ -259,7 +270,7 @@ pub export fn uhdk_app_destroy(handle: ?*c.uhdk_app) void {
std.debug.assert(c.z_native_is_main_thread() != 0 and !application.running);
runtime.destroyEvents(application.published);
runtime.destroyEvents(application.pending_head);
application.events.deinit(application.gpa);
application.scratch.deinit();
var cursor = application.objects;
while (cursor) |object| {
cursor = object.next;

520
src/arenas.zig Normal file
View File

@ -0,0 +1,520 @@
//! UHDK-owned scratch arena and supporting paged storage.
//! Derived from RibtechEngine lib/p2 at 39697f9afba2933f92361586bf8802c22dfbe709.
//! Maintained as part of UHDK; this is not a vendored dependency.
const std = @import("std");
pub const PoolStatistics = struct {
statName: []const u8,
instanceCount: usize,
memoryReserved: usize,
memoryUsed: usize,
};
// mutex based, fast bump-only allocator
pub const BumpArena = struct {
const PageSize = 8192 * 2;
const SmallAllocPages = PagedVectorAdvanced([PageSize]u8, 1);
smallBumpOffset: usize = 0,
pageIndex: usize = 0,
allocatedBytes: usize = 0,
allocCount: usize = 0,
backing: std.mem.Allocator,
io_context: std.Io,
small: SmallAllocPages,
large: std.heap.ArenaAllocator,
mutex: std.Io.Mutex,
pub var vtable: std.mem.Allocator.VTable = .{
.alloc = alloc,
.free = free,
.resize = resize,
.remap = std.mem.Allocator.noRemap,
};
pub fn usage(self: *@This()) usize {
return self.allocatedBytes;
}
pub fn getStatistics(self: *@This()) PoolStatistics {
return .{
.statName = "BumpArena",
.instanceCount = self.allocCount,
.memoryReserved = self.small.pages.items.len * PageSize +
self.small.pages.capacity * @sizeOf(usize) +
self.large.queryCapacity(),
.memoryUsed = self.allocatedBytes,
};
}
pub fn init(backing: std.mem.Allocator, io_context: std.Io) !@This() {
return .{
.backing = backing,
.io_context = io_context,
.small = try SmallAllocPages.init(backing),
.large = std.heap.ArenaAllocator.init(backing),
.mutex = .init,
};
}
pub fn io(self: *const @This()) std.Io {
return self.io_context;
}
pub fn deinit(self: *@This()) void {
self.mutex.lockUncancelable(self.io());
defer self.mutex.unlock(self.io());
self.small.deinit(self.backing);
self.large.deinit();
}
pub fn alloc(ctx: *anyopaque, len: usize, ptr_align: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
const self: *@This() = @ptrCast(@alignCast(ctx));
self.mutex.lockUncancelable(self.io());
defer self.mutex.unlock(self.io());
const alignment = ptr_align.toByteUnits();
const result: [*]u8 = if (len > 4096 or alignment > 8) result: {
break :result self.largeAllocator().rawAlloc(len, ptr_align, ret_addr) orelse return null;
} else result: {
var offset = std.mem.alignForward(usize, self.smallBumpOffset, alignment);
if (offset + len > PageSize) {
// The first page exists at init; materialize its logical entry
// before advancing. append is transactional on allocation failure.
if (self.small.pages.items[0].len == 0) self.small.pages.items[0].len = 1;
self.small.append(self.backing, undefined) catch return null;
self.pageIndex += 1;
offset = 0;
}
const memory = self.small.getMutable(self.pageIndex)[offset .. offset + len];
self.smallBumpOffset = offset + len;
break :result memory.ptr;
};
self.allocatedBytes += len;
self.allocCount += 1;
return result;
}
inline fn largeAllocator(self: *@This()) std.mem.Allocator {
return self.large.allocator();
}
pub fn free(ctx: *anyopaque, buf: []u8, buf_align: std.mem.Alignment, ret_addr: usize) void {
// it's a bump allocator, it does nothing on free.
_ = ctx;
_ = buf;
_ = buf_align;
_ = ret_addr;
}
// do not support resize in place.
pub fn resize(ctx: *anyopaque, buf: []u8, buf_align: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
_ = ctx;
_ = buf;
_ = buf_align;
_ = new_len;
_ = ret_addr;
return false;
}
pub fn allocator(self: *@This()) std.mem.Allocator {
return .{ .ptr = self, .vtable = &vtable };
}
pub fn reset(self: *@This()) void {
self.mutex.lockUncancelable(self.io());
defer self.mutex.unlock(self.io());
_ = self.large.reset(.free_all);
self.small.reset(self.backing);
self.smallBumpOffset = 0;
self.pageIndex = 0;
self.allocatedBytes = 0;
self.allocCount = 0;
}
};
test "bump arena initialization" {
var arena = try BumpArena.init(std.testing.allocator, std.testing.io);
defer arena.deinit();
}
test "reset clears usage" {
var arena = try BumpArena.init(std.testing.allocator, std.testing.io);
defer arena.deinit();
const allocator = arena.allocator();
const mem = allocator.alloc(u8, 128) catch unreachable;
defer _ = mem;
try std.testing.expect(arena.usage() >= 128);
arena.reset();
try std.testing.expectEqual(@as(usize, 0), arena.usage());
}
// it's like an normal vector/ arraylist except it never invalidates old pointers.
// and never causes re-allocations. (excepting in the control block, which is just a vector of things)
//
// It is implemented with a control block which which contains a list of pages.
// these pages contain the actual memory of the vector.
//
// it has a growth factor, which can be configured.
//
// use cases:
//
// - this is highly performant for cases where
// - YOU WANT generally fast insertion time
// - YOU WANT fast random access time
// - YOU WANT cache-coherent and efficient linear iteration
// - YOU WANT to retain pointers across operations
// - YOU WANT to avoid reallocations (eg. if the data set is remarkably big)
// - YOU KNOW that you have linear growth factors and exponential growth is overkill.
//
// great for:
// - event logging.
// - output buffering.
// - recording timelines.
pub fn PagedVectorAdvanced(comptime T: type, comptime growth: usize) type {
return struct {
const Page = struct {
data: [growth]T align(8) = undefined,
len: u32 = 0,
};
pages: std.ArrayListUnmanaged(*Page) = .empty,
currentPageId: usize = 0,
head: *T,
pub fn init(allocator: std.mem.Allocator) !@This() {
var rv = @This(){
.head = undefined,
};
const page = try allocator.create(Page);
errdefer allocator.destroy(page);
page.* = .{};
try rv.pages.append(allocator, page);
rv.head = &rv.pages.items[0].data[0];
return rv;
}
pub fn capacity(self: @This()) usize {
return self.pages.items.len * growth;
}
pub fn len(self: @This()) usize {
return (self.currentPageId) * growth + self.currentPage().len;
}
pub fn getStatistics(self: *const @This()) PoolStatistics {
return .{
.statName = "PagedVector",
.instanceCount = self.len(),
.memoryReserved = self.pages.items.len * @sizeOf(Page) +
self.pages.capacity * @sizeOf(*Page),
.memoryUsed = self.len() * @sizeOf(T),
};
}
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
for (self.pages.items) |page| {
allocator.destroy(page);
}
self.pages.deinit(allocator);
}
fn maybeExpand(self: *@This(), allocator: std.mem.Allocator) !void {
if (self.currentPageId == self.pages.items.len - 1) {
const page = try allocator.create(Page);
errdefer allocator.destroy(page);
page.* = .{};
try self.pages.append(allocator, page);
self.currentPageId = self.pages.items.len - 1;
} else {
self.currentPageId += 1;
}
}
// Retain pages; no live element survives reset.
pub fn reset(self: *@This(), allocator: std.mem.Allocator) void {
_ = allocator;
for (self.pages.items) |page| page.len = 0;
self.currentPageId = 0;
self.head = &self.pages.items[0].data[0];
}
pub fn append(self: *@This(), allocator: std.mem.Allocator, value: T) !void {
if (self.currentPage().len == growth) try self.maybeExpand(allocator);
const page = self.currentPage();
self.head = &page.data[page.len];
self.head.* = value;
page.len += 1;
}
pub fn appendSlice(self: *@This(), allocator: std.mem.Allocator, slice: []const T) !void {
for (slice) |value| {
try self.append(allocator, value);
}
}
pub fn getMutable(self: *@This(), index: usize) *T {
const pageIndex = std.math.divFloor(usize, index, growth) catch unreachable;
return &self.pages.items[pageIndex].data[index % growth];
}
pub fn get(self: @This(), index: usize) *const T {
const pageIndex = std.math.divFloor(usize, index, growth) catch unreachable;
return &self.pages.items[pageIndex].data[index % growth];
}
// swaps the element at the index with the element currently pointed to by head
// then pops back the head, shrinking length by 1
//
// Efficient removal of an element at an index but does not preserve order.
pub fn swapRemove(self: *@This(), index: usize) void {
const ptr = self.getMutable(index);
ptr.* = self.head.*;
_ = try self.pop();
}
fn currentPage(self: @This()) *Page {
return self.pages.items[self.currentPageId];
}
// // retrieves a value, shrinking length by 1
pub fn pop(self: *@This()) !T {
const rv = self.head.*;
var page = self.currentPage();
page.len -= 1;
if (page.len < 1) {
page.len = 0;
self.currentPageId -= 1;
}
self.head = &self.currentPage().data[self.currentPage().len - 1];
return rv;
}
};
}
pub fn PagedVectorUnmanaged(comptime T: type) type {
return PagedVectorAdvanced(T, 1024);
}
pub fn PagedVector(comptime T: type) type {
return struct {
const VectorType = PagedVectorAdvanced(T, 1024);
vector: VectorType,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !@This() {
return .{
.vector = try VectorType.init(allocator),
.allocator = allocator,
};
}
pub fn deinit(self: *@This()) void {
self.vector.deinit(self.allocator);
}
pub fn appendSlice(self: *@This(), slice: []const T) !void {
try self.vector.appendSlice(self.allocator, slice);
}
pub fn append(self: *@This(), value: T) !void {
try self.vector.append(self.allocator, value);
}
pub fn appendAndGet(self: *@This(), value: T) !*T {
const index = self.len();
try self.append(value);
return self.getMutable(index);
}
pub fn get(self: *const @This(), index: usize) *const T {
return self.vector.get(index);
}
pub fn getMutable(self: *@This(), index: usize) *T {
return self.vector.getMutable(index);
}
pub fn len(self: @This()) usize {
return self.vector.len();
}
pub fn capacity(self: @This()) usize {
return self.vector.capacity();
}
pub fn getStatistics(self: *const @This()) PoolStatistics {
return self.vector.getStatistics();
}
pub fn swapRemove(self: *@This(), index: usize) void {
self.vector.swapRemove(index);
}
pub fn reset(self: *@This()) void {
self.vector.reset(self.allocator);
}
// // retrieves a value, shrinking length by 1
pub fn pop(self: *@This()) !T {
return try self.vector.pop();
}
};
}
test "simple loading multiple pages." {
const TestStruct = struct {
lmao: usize,
};
const allocator = std.testing.allocator;
var vector = try PagedVectorAdvanced(TestStruct, 1024).init(std.testing.allocator);
defer vector.deinit(allocator);
for (0..128) |j| {
try vector.append(allocator, .{ .lmao = j });
}
const ptr = vector.get(0);
const mutable = vector.getMutable(127);
for (0..4096) |i| {
try vector.append(allocator, .{ .lmao = i });
}
// testing that pointers are not invalidated
std.debug.assert(ptr == vector.get(0));
std.debug.assert(mutable == vector.getMutable(127));
std.debug.assert(vector.getMutable(4092) == vector.getMutable(4092));
// 4096 + 128 = 4224 = 4 pages + 128 extra entries
std.debug.assert(vector.pages.items.len == 5);
std.debug.assert(vector.capacity() == 1024 * 5);
std.debug.assert(vector.len() == 128 + 1024 * 4);
// 4092 - 3072 = offset of 1020 in the third page.
const ptr4092 = vector.getMutable(4092);
std.debug.assert(vector.getMutable(4092) == &vector.pages.items[3].data[1020]);
// lets try adding 1024 elements and see if pointers are still preserved.
for (0..1024) |_| {
try vector.append(allocator, .{ .lmao = 0 });
}
std.debug.assert(vector.getMutable(4092) == ptr4092);
std.debug.assert(vector.get(0).lmao == 0);
std.debug.assert(vector.get(1).lmao == 1);
std.debug.assert(vector.get(2).lmao == 2);
// testing the pop() function
std.debug.assert(vector.len() == 128 + 1024 * 5);
_ = try vector.pop();
_ = try vector.pop();
_ = try vector.pop();
// testing the swapRemove Functionality
std.debug.assert(vector.len() == 128 + 1024 * 5 - 3);
vector.swapRemove(1);
std.debug.assert(vector.len() == 128 + 1024 * 5 - 4);
for (0..1024) |_| {
vector.swapRemove(0);
}
std.debug.assert(vector.len() == 128 + 1024 * 5 - 4 - 1024);
}
test "managed version" {
const allocator = std.testing.allocator;
var vec = try PagedVector(struct { lmao: u64 }).init(allocator);
defer vec.deinit();
for (0..8192) |i| {
try vec.append(.{ .lmao = i });
}
std.debug.assert(vec.vector.pages.items.len == 8);
}
test "reset retains pages and clears length" {
const allocator = std.testing.allocator;
var vec = try PagedVectorAdvanced(u32, 4).init(allocator);
defer vec.deinit(allocator);
for (0..10) |i| {
try vec.append(allocator, @intCast(i));
}
try std.testing.expect(vec.pages.items.len > 1);
vec.reset(allocator);
try std.testing.expectEqual(@as(usize, 3), vec.pages.items.len);
try std.testing.expectEqual(@as(usize, 0), vec.len());
try vec.append(allocator, 42);
try std.testing.expectEqual(@as(u32, 42), vec.get(0).*);
}
fn exercise(gpa: std.mem.Allocator) !void {
var arena = try BumpArena.init(gpa, std.testing.io);
defer arena.deinit();
const a = arena.allocator();
const first = try a.alloc(u8, 4096);
@memset(first, 17);
_ = try a.alloc(u8, 4096);
_ = try a.alloc(u8, 4096);
_ = try a.alloc(u8, 4093);
// Alignment rounding takes this allocation across the first page boundary.
const aligned = try a.create(u64);
aligned.* = 0x12345678;
try std.testing.expectEqual(@as(usize, 0), @intFromPtr(aligned) % @alignOf(u64));
const OverAligned = struct { value: u8 align(256) };
const over = try a.create(OverAligned);
over.* = .{ .value = 42 };
try std.testing.expectEqual(@as(usize, 0), @intFromPtr(over) % 256);
const large = try a.alloc(u8, 20000);
@memset(large, 29);
for (first) |v| try std.testing.expectEqual(@as(u8, 17), v);
try std.testing.expectEqual(@as(u64, 0x12345678), aligned.*);
arena.reset();
try std.testing.expectEqual(@as(usize, 0), arena.usage());
const reused = try a.alloc(u8, 4096);
try std.testing.expectEqual(@intFromPtr(first.ptr), @intFromPtr(reused.ptr));
for (0..12) |_| _ = try a.alloc(u8, 4096);
arena.reset();
for (0..12) |_| _ = try a.alloc(u8, 4096);
}
test "bump arena alignment page rollover reuse and all allocation failures" {
try std.testing.checkAllAllocationFailures(std.testing.allocator, exercise, .{});
}
test "bump arena failed page allocation can be retried" {
var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
var arena = try BumpArena.init(failing.allocator(), std.testing.io);
defer arena.deinit();
const a = arena.allocator();
for (0..4) |_| _ = try a.alloc(u8, 4096);
const before = arena.usage();
failing.fail_index = failing.alloc_index;
try std.testing.expectError(error.OutOfMemory, a.alloc(u8, 4096));
try std.testing.expectEqual(before, arena.usage());
failing.fail_index = std.math.maxInt(usize);
const memory = try a.alloc(u8, 4096);
@memset(memory, 7);
try std.testing.expectEqual(before + 4096, arena.usage());
}

View File

@ -4,7 +4,7 @@ const c = @import("native");
const abi = @import("abi.zig");
const runtime = @import("runtime.zig");
export fn z_emit(handle: ?*c.uhdk_app, event: [*c]const c.uhdk_event) void {
export fn z_emit(handle: ?*c.uhdk_app, event: [*c]const c.z_event) void {
runtime.emit(handle, event);
}
@ -12,7 +12,7 @@ export fn z_complete(
handle: ?*c.uhdk_app,
id: u64,
status: abi.Status,
result: [*c]const c.uhdk_request_result,
result: [*c]const c.z_request_result,
) void {
runtime.complete(handle, id, status, result);
}
@ -38,7 +38,7 @@ export fn z_metrics(handle: ?*c.uhdk_window, value: [*c]const c.uhdk_window_metr
object.app.lock();
object.payload.window.metrics = value.*;
object.app.unlock();
var event = std.mem.zeroes(c.uhdk_event);
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_WINDOW_METRICS;
event.window = handle;
event.data.metrics = value.*;
@ -50,7 +50,7 @@ export fn z_preferences(handle: ?*c.uhdk_app, value: [*c]const c.uhdk_preference
application.lock();
application.preferences = value.*;
application.unlock();
var event = std.mem.zeroes(c.uhdk_event);
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_PREFERENCES_CHANGED;
event.data.preferences = value.*;
c.z_emit(handle, &event);

View File

@ -7,7 +7,7 @@ const abi = @import("abi.zig");
fn destroyApplication(application: *Application) void {
runtime.destroyEvents(application.pending_head);
runtime.destroyEvents(application.published);
application.events.deinit(application.gpa);
application.scratch.deinit();
const gpa = application.gpa;
gpa.free(application.name);
application.* = undefined;
@ -37,7 +37,7 @@ test "publication copies text and preserves batches across ordinary calls" {
const application = try Application.create(std.testing.allocator, std.testing.io, "events");
defer destroyApplication(application);
var source = [_]u8{ 'h', 'i' };
var event = std.mem.zeroes(c.uhdk_event);
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_TEXT_COMMIT;
event.data.text.text = .{ .ptr = &source, .len = source.len };
runtime.emit(runtime.toAppHandle(application), &event);
@ -99,3 +99,68 @@ test "tick statistics exclude blocked time" {
try std.testing.expectEqual(@as(u64, 1), stats.tick_count);
try std.testing.expect(stats.tick_time_ns < wall_time / 2);
}
test "public payload scratch lifetime empty polls and resource pins" {
const app_api = @import("api/app.zig");
const application = try Application.create(std.testing.allocator, std.testing.io, "payloads");
defer destroyApplication(application);
application.running = true;
application.worker_id = std.Thread.getCurrentId();
const handle = runtime.toAppHandle(application);
const file = try runtime.createObject(application, c.Z_KIND_FILE);
defer runtime.release(file);
var handles = [_]?*c.uhdk_file{@ptrCast(file)};
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_OPEN_FILES;
event.data.files = .{ .ptr = &handles, .len = 1 };
runtime.emit(handle, &event);
try std.testing.expectEqual(@as(usize, 2), file.refs);
var batch: c.uhdk_event_list = undefined;
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 1), batch.len);
try std.testing.expectEqual(@as(u64, @sizeOf(c.uhdk_file_list)), batch.ptr[0].data_size);
const payload: *const c.uhdk_file_list = @ptrCast(@alignCast(batch.ptr[0].data.?));
try std.testing.expectEqual(@intFromPtr(file), @intFromPtr(payload.ptr[0].?));
try std.testing.expectEqual(@as(usize, 2), file.refs);
// Invalid calls must not reset a borrow. Valid empty polls must release it.
try std.testing.expectEqual(@as(abi.Status, abi.INVALID), app_api.uhdk_app_poll_events(handle, 0, null));
try std.testing.expectEqual(@as(u64, 1), payload.len);
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 0), batch.len);
try std.testing.expectEqual(@as(usize, 1), file.refs);
try std.testing.expectEqual(@as(usize, 0), application.scratch.usage());
}
test "poll allocation failure keeps queued terminal completion retryable" {
const app_api = @import("api/app.zig");
var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
const application = try Application.create(failing.allocator(), std.testing.io, "retry");
defer destroyApplication(application);
application.running = true;
application.worker_id = std.Thread.getCurrentId();
const handle = runtime.toAppHandle(application);
var bytes = [_]u8{42} ** 20000;
var event = std.mem.zeroes(c.z_event);
event.type = c.UHDK_EVENT_REQUEST_DONE;
event.data.request = .{ .id = 123, .kind = c.UHDK_REQUEST_FILE_READ, .status = abi.OK, .diagnostic = .{ .ptr = null, .len = 0 }, .value = .{ .bytes = .{ .ptr = &bytes, .len = bytes.len } } };
runtime.emit(handle, &event);
@memset(&bytes, 0);
failing.fail_index = failing.alloc_index;
var batch: c.uhdk_event_list = undefined;
try std.testing.expectEqual(@as(abi.Status, abi.OOM), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 0), batch.len);
try std.testing.expect(application.pending_head != null);
failing.fail_index = std.math.maxInt(usize);
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 1), batch.len);
const result: *const c.uhdk_request_result = @ptrCast(@alignCast(batch.ptr[0].data.?));
try std.testing.expectEqual(@as(u64, 123), result.id);
const data: *const c.uhdk_bytes = @ptrCast(@alignCast(result.data.?));
try std.testing.expectEqual(@as(u64, bytes.len), data.len);
for (data.ptr[0..@intCast(data.len)]) |b| try std.testing.expectEqual(@as(u8, 42), b);
try std.testing.expect(application.pending_head == null);
// Producer payload storage has been freed; the public copy is in scratch.
try std.testing.expectEqual(@as(usize, 0), application.published.?.arena.queryCapacity());
try std.testing.expectEqual(@as(abi.Status, abi.OK), app_api.uhdk_app_poll_events(handle, 0, &batch));
try std.testing.expectEqual(@as(u64, 0), batch.len);
}

View File

@ -75,7 +75,7 @@ static uhdk_modifier_flags mods(NSEventModifierFlags f) {
@property(nonatomic) uint64_t currentInput;
@property(nonatomic) uint64_t lastEdit;
@property(nonatomic) uhdk_rect caret;
- (void)emit:(uhdk_event *)event;
- (void)emit:(z_event *)event;
- (void)cancelMarked;
- (void)applyState:(const uhdk_text_state *)state;
@end
@ -197,14 +197,14 @@ static uhdk_key logical(NSEvent *e) {
(void)event;
return YES;
}
- (void)emit:(uhdk_event *)e {
- (void)emit:(z_event *)e {
e->window = self.owner.handle;
e->input_id = self.currentInput;
z_emit(host.app, e);
}
- (void)drawRect:(NSRect)rect {
(void)rect;
uhdk_event e = {.type = UHDK_EVENT_WINDOW_REDRAW};
z_event e = {.type = UHDK_EVENT_WINDOW_REDRAW};
[self emit:&e];
}
- (void)setFrameSize:(NSSize)size {
@ -236,7 +236,7 @@ static uhdk_key logical(NSEvent *e) {
}
- (void)key:(NSEvent *)event pressed:(BOOL)pressed {
self.currentInput = ++self.nextInput;
uhdk_event e = {.type = UHDK_EVENT_KEY};
z_event e = {.type = UHDK_EVENT_KEY};
e.data.key.physical_key = physical(event.keyCode);
e.data.key.logical_key = logical(event);
e.data.key.modifiers = mods(event.modifierFlags);
@ -277,7 +277,7 @@ static uhdk_key logical(NSEvent *e) {
break;
}
self.currentInput = ++self.nextInput;
uhdk_event e = {.type = UHDK_EVENT_KEY};
z_event e = {.type = UHDK_EVENT_KEY};
e.data.key.physical_key = physical(event.keyCode);
e.data.key.modifiers = newMods;
e.data.key.pressed =
@ -291,7 +291,7 @@ static uhdk_key logical(NSEvent *e) {
}
- (void)pointer:(NSEvent *)event type:(uint32_t)type pressed:(BOOL)pressed {
NSPoint p = [self convertPoint:event.locationInWindow fromView:nil];
uhdk_event e = {.type = type};
z_event e = {.type = type};
e.data.pointer.position = (uhdk_point){p.x, p.y};
e.data.pointer.delta = (uhdk_point){event.deltaX, event.deltaY};
e.data.pointer.modifiers = mods(event.modifierFlags);
@ -341,7 +341,7 @@ static uhdk_key logical(NSEvent *e) {
}
- (void)scrollWheel:(NSEvent *)event {
NSPoint p = [self convertPoint:event.locationInWindow fromView:nil];
uhdk_event e = {.type = UHDK_EVENT_SCROLL};
z_event e = {.type = UHDK_EVENT_SCROLL};
e.data.scroll.position = (uhdk_point){p.x, p.y};
e.data.scroll.delta = (uhdk_point){-event.scrollingDeltaX, -event.scrollingDeltaY};
e.data.scroll.unit =
@ -383,7 +383,7 @@ static uhdk_key logical(NSEvent *e) {
selection:(NSRange)sel
explicit:(BOOL)explicitRange {
[self editID];
uhdk_event e = {.type = type};
z_event e = {.type = type};
e.data.text.text = utf8(s);
e.data.text.revision = self.revision;
e.data.text.has_replacement = explicitRange;
@ -418,7 +418,7 @@ static uhdk_key logical(NSEvent *e) {
[self.textBuffer replaceCharactersInRange:replace withString:s];
self.selection = NSMakeRange(replace.location + s.length, 0);
if (hadMarked) {
uhdk_event e = {.type = UHDK_EVENT_TEXT_COMPOSITION_END};
z_event e = {.type = UHDK_EVENT_TEXT_COMPOSITION_END};
[self emit:&e];
}
if (ownInput)
@ -463,7 +463,7 @@ static uhdk_key logical(NSEvent *e) {
self.selection = self.originalRange;
self.marked = NSMakeRange(NSNotFound, 0);
[self editID];
uhdk_event e = {.type = UHDK_EVENT_TEXT_COMPOSITION_END};
z_event e = {.type = UHDK_EVENT_TEXT_COMPOSITION_END};
[self emit:&e];
}
[self.inputContext discardMarkedText];
@ -542,7 +542,7 @@ static uhdk_key logical(NSEvent *e) {
return;
}
[self editID];
uhdk_event e = {.type = UHDK_EVENT_EDIT_COMMAND};
z_event e = {.type = UHDK_EVENT_EDIT_COMMAND};
e.data.edit.command = command.unsignedIntValue;
e.data.edit.extend_selection = extend;
[self emit:&e];
@ -608,7 +608,7 @@ static uhdk_key logical(NSEvent *e) {
files[n++] = f;
}
NSPoint point = [self convertPoint:sender.draggingLocation fromView:nil];
uhdk_event e = {.type = UHDK_EVENT_DROP_FILES};
z_event e = {.type = UHDK_EVENT_DROP_FILES};
e.data.drop_files.files = (uhdk_file_list){files, n};
e.data.drop_files.position = (uhdk_point){point.x, point.y};
[self emit:&e];
@ -640,12 +640,12 @@ static uhdk_key logical(NSEvent *e) {
: self.window.isZoomed ? UHDK_WINDOW_MAXIMIZED
: UHDK_WINDOW_NORMAL;
z_metrics(self.handle, &m);
uhdk_event e = {.type = UHDK_EVENT_WINDOW_REDRAW, .window = self.handle};
z_event e = {.type = UHDK_EVENT_WINDOW_REDRAW, .window = self.handle};
z_emit(host.app, &e);
}
- (BOOL)windowShouldClose:(NSWindow *)sender {
(void)sender;
uhdk_event e = {.type = UHDK_EVENT_WINDOW_CLOSE_REQUESTED, .window = self.handle};
z_event e = {.type = UHDK_EVENT_WINDOW_CLOSE_REQUESTED, .window = self.handle};
z_emit(host.app, &e);
return NO;
}
@ -668,7 +668,7 @@ static uhdk_key logical(NSEvent *e) {
[self.view cancelMarked];
if (self.view.capture) {
self.view.capture = NO;
uhdk_event e = {.type = UHDK_EVENT_POINTER_MODE_CHANGED, .window = self.handle};
z_event e = {.type = UHDK_EVENT_POINTER_MODE_CHANGED, .window = self.handle};
e.data.pointer_mode = UHDK_POINTER_NORMAL;
z_emit(host.app, &e);
}
@ -709,7 +709,7 @@ static uhdk_key logical(NSEvent *e) {
w = candidate;
break;
}
uhdk_event e = {.type = UHDK_EVENT_MENU_COMMAND, .window = w.handle};
z_event e = {.type = UHDK_EVENT_MENU_COMMAND, .window = w.handle};
e.data.menu.command_id = self.command;
if (self.role == UHDK_MENU_ROLE_QUIT) {
e.type = UHDK_EVENT_QUIT_REQUESTED;
@ -808,7 +808,7 @@ static NSMenu *makeMenu(uhdk_menu *menu, ZWindow *window) {
@implementation ZHost
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
(void)sender;
uhdk_event e = {.type = UHDK_EVENT_QUIT_REQUESTED};
z_event e = {.type = UHDK_EVENT_QUIT_REQUESTED};
z_emit(self.app, &e);
return NSTerminateCancel;
}
@ -818,13 +818,13 @@ static NSMenu *makeMenu(uhdk_menu *menu, ZWindow *window) {
}
- (void)applicationDidBecomeActive:(NSNotification *)note {
(void)note;
uhdk_event e = {.type = UHDK_EVENT_APP_ACTIVATION};
z_event e = {.type = UHDK_EVENT_APP_ACTIVATION};
e.data.active = YES;
z_emit(self.app, &e);
}
- (void)applicationDidResignActive:(NSNotification *)note {
(void)note;
uhdk_event e = {.type = UHDK_EVENT_APP_ACTIVATION};
z_event e = {.type = UHDK_EVENT_APP_ACTIVATION};
e.data.active = NO;
z_emit(self.app, &e);
}
@ -838,7 +838,7 @@ static NSMenu *makeMenu(uhdk_menu *menu, ZWindow *window) {
if (f)
files[n++] = f;
}
uhdk_event e = {.type = UHDK_EVENT_OPEN_FILES};
z_event e = {.type = UHDK_EVENT_OPEN_FILES};
e.data.files = (uhdk_file_list){files, n};
z_emit(self.app, &e);
for (NSUInteger i = 0; i < n; i++)
@ -967,7 +967,7 @@ static void watchEvents(ConstFSEventStreamRef stream, void *info, size_t count,
[[path stringByDeletingLastPathComponent] isEqualToString:watch.path]);
if (!match && !unknown && !invalid && ![path isEqualToString:watch.observed])
continue;
uhdk_event e = {.type = UHDK_EVENT_WATCH};
z_event e = {.type = UHDK_EVENT_WATCH};
e.data.watch.watch = watch.handle;
e.data.watch.status = UHDK_STATUS_OK;
e.data.watch.flags = UHDK_CHANGE_RESCAN_REQUIRED;
@ -1091,7 +1091,7 @@ static uhdk_status showPanel(uhdk_app *app, const uhdk_file_dialog_desc *d, uint
if (f)
files[n++] = f;
}
uhdk_request_result result = {0};
z_request_result result = {0};
result.value.files = (uhdk_file_list){files, n};
z_complete(app, request, n == urls.count ? UHDK_STATUS_OK : UHDK_STATUS_OUT_OF_MEMORY,
&result);
@ -1260,7 +1260,7 @@ int z_native_is_main_thread(void) {
return NSThread.isMainThread;
}
uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t request,
uhdk_request_result *result) {
z_request_result *result) {
@autoreleasepool {
@try {
ZWindow *w = cmd->target && z_object_kind(cmd->target) == Z_KIND_WINDOW
@ -1429,7 +1429,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
case Z_POINTER:
w.view.capture = cmd->number != 0;
{
uhdk_event e = {.type = UHDK_EVENT_POINTER_MODE_CHANGED, .window = w.handle};
z_event e = {.type = UHDK_EVENT_POINTER_MODE_CHANGED, .window = w.handle};
e.data.pointer_mode = (uint32_t)cmd->number;
z_emit(app, &e);
}
@ -1561,7 +1561,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
break;
}
case Z_REDRAW: {
uhdk_event e = {.type = UHDK_EVENT_WINDOW_REDRAW, .window = w.handle};
z_event e = {.type = UHDK_EVENT_WINDOW_REDRAW, .window = w.handle};
z_emit(app, &e);
break;
}

View File

@ -1,8 +1,49 @@
#ifndef UHDK_PRIVATE_NATIVE_H
#define UHDK_PRIVATE_NATIVE_H
#define UHDK_ENABLE_NATIVE
#include "uhdk.h"
#include "uhdk_native.h"
#include <stddef.h>
/* Private producer storage. Converted to public pointer payloads at poll. */
typedef struct z_request_result {
uhdk_request_id id;
uhdk_request_kind kind;
uhdk_status status;
uhdk_text diagnostic; /* Optional; for humans, never parse for logic. */
union {
uhdk_file_list files; /* FILE_DIALOG */
uhdk_text text; /* CLIPBOARD_TEXT */
uhdk_window *window;
uhdk_gl_context *gl_context;
uhdk_watch *watch;
uhdk_bytes bytes;
uhdk_file_info info;
} value; /* Only valid on UHDK_STATUS_OK; SHELL/COMMAND/FILE_WRITE/MENU_POPUP have no payload. */
} z_request_result;
typedef struct z_event {
uint32_t struct_size;
uhdk_event_type type;
uint64_t time_ns;
uint64_t input_id; /* Correlates raw key and native edit/text; zero for non-input. */
uhdk_window *window;
union {
uhdk_bool active;
uhdk_file_list files;
uhdk_window_metrics metrics;
uhdk_preferences preferences;
uhdk_key_event key;
uhdk_pointer_event pointer;
uhdk_pointer_mode pointer_mode;
uhdk_scroll_event scroll;
uhdk_text_event text;
uhdk_edit_event edit;
uhdk_drop_files_event drop_files;
uhdk_menu_event menu;
uhdk_watch_event watch;
z_request_result request;
} data;
} z_event;
enum {
Z_EVENT_SIZE = sizeof(uhdk_event),
Z_EVENT_ALIGNMENT = _Alignof(uhdk_event),
@ -67,7 +108,7 @@ void z_native_wake(void);
void z_native_again(void);
void z_native_stop(void);
void z_native_finish(void);
uhdk_status z_native_execute(uhdk_app *, const z_command *, uint64_t, uhdk_request_result *);
uhdk_status z_native_execute(uhdk_app *, const z_command *, uint64_t, z_request_result *);
void z_native_cancel(uint64_t);
void z_native_destroy(uint32_t kind, void *native);
void *z_native_pool_push(void);
@ -79,8 +120,8 @@ void z_native_handles(void *, uhdk_native_window *);
uhdk_status z_native_file_init(uhdk_file *, uhdk_bytes path, uhdk_text uri, void *grant);
/* Zig callbacks, always return before native temporary spans expire. */
void z_tick(uhdk_app *);
void z_emit(uhdk_app *, const uhdk_event *);
void z_complete(uhdk_app *, uint64_t, uhdk_status, const uhdk_request_result *);
void z_emit(uhdk_app *, const z_event *);
void z_complete(uhdk_app *, uint64_t, uhdk_status, const z_request_result *);
void *z_object_native(void *);
void z_object_set_native(void *, void *);
uint32_t z_object_kind(void *);

View File

@ -17,6 +17,7 @@ test {
std.testing.refAllDecls(@import("abi.zig"));
std.testing.refAllDecls(@import("FileOperation.zig"));
_ = @import("core_tests.zig");
_ = @import("arenas.zig");
}
fn checkExports(comptime module: type) void {

View File

@ -111,7 +111,7 @@ pub fn destroyEvents(head: ?*Event) void {
}
pub fn publishLocked(application: *Application, node: *Event) void {
node.event.struct_size = @sizeOf(c.uhdk_event);
node.event.struct_size = @sizeOf(c.z_event);
node.event.time_ns = now(application.io) - application.epoch;
node.next = null;
if (application.pending_tail) |tail| tail.next = node else application.pending_head = node;
@ -153,7 +153,7 @@ pub fn copyPayload(node: *Event) !void {
}
pub fn simpleEvent(application: *Application, typ: u32) void {
var event = std.mem.zeroes(c.uhdk_event);
var event = std.mem.zeroes(c.z_event);
event.type = typ;
c.z_emit(toAppHandle(application), &event);
}
@ -256,7 +256,7 @@ pub fn finish(
application: *Application,
command: *Request,
status: abi.Status,
value: ?*const c.uhdk_request_result,
value: ?*const c.z_request_result,
) void {
if (command.id == 0) {
abandon(command);
@ -478,7 +478,7 @@ pub fn fileStep(application: *Application, command: *Request) bool {
finish(application, command, fileError(err), null);
return true;
};
var value = std.mem.zeroes(c.uhdk_request_result);
var value = std.mem.zeroes(c.z_request_result);
switch (result) {
.pending => return false,
.written => {},
@ -554,7 +554,7 @@ pub fn setMenuFlags(object: *Resource, id: u64, flags: u32) bool {
return false;
}
pub fn emit(handle: ?*c.uhdk_app, event: [*c]const c.uhdk_event) void {
pub fn emit(handle: ?*c.uhdk_app, event: [*c]const c.z_event) void {
const application = fromAppHandle(handle);
const node = application.gpa.create(Event) catch {
application.lock();
@ -599,7 +599,7 @@ pub fn complete(
handle: ?*c.uhdk_app,
id: u64,
status: abi.Status,
result: [*c]const c.uhdk_request_result,
result: [*c]const c.z_request_result,
) void {
const application = fromAppHandle(handle);
application.lock();
@ -647,7 +647,7 @@ pub fn tick(handle: ?*c.uhdk_app) void {
application.io_tail = command;
continue;
}
var result = std.mem.zeroes(c.uhdk_request_result);
var result = std.mem.zeroes(c.z_request_result);
if (command.data.op == c.Z_MENU_FLAGS) {
application.lock();
const found = setMenuFlags(fromObjectHandle(command.data.target), command.data.number, @intCast(command.data.limit));

View File

@ -148,7 +148,7 @@ static Window *windowOf(void *handle) {
return handle ? z_object_native(handle) : NULL;
}
static void emit(Window *w, uint32_t type) {
uhdk_event e = {0};
z_event e = {0};
e.type = type;
e.window = w ? w->handle : NULL;
z_emit(host.app, &e);
@ -209,7 +209,7 @@ static void textEvent(Window *w, uint32_t type, const WCHAR *text, int length, i
char *s = utf8(text, length, &len);
if (!s)
return;
uhdk_event e = {0};
z_event e = {0};
e.type = type;
e.window = w->handle;
e.input_id = w->input_id = ++host.input_id;
@ -228,7 +228,7 @@ static void textEvent(Window *w, uint32_t type, const WCHAR *text, int length, i
profileFree(s);
}
static void editEvent(Window *w, uint32_t command) {
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_EDIT_COMMAND;
e.window = w->handle;
e.input_id = w->input_id = ++host.input_id;
@ -248,7 +248,7 @@ static void menuEvent(Window *w, const uhdk_menu_item *item) {
editEvent(w, UHDK_EDIT_COPY + item->role - UHDK_MENU_ROLE_COPY);
return;
}
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_MENU_COMMAND;
e.window = w ? w->handle : NULL;
e.data.menu.command_id = item->command_id;
@ -445,7 +445,7 @@ static void dropFiles(Window *w, HDROP drop) {
POINT point;
DragQueryPoint(drop, &point);
if (used) {
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_DROP_FILES;
e.window = w->handle;
e.data.drop_files.files = (uhdk_file_list){files, used};
@ -511,7 +511,7 @@ static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
metrics(w);
break;
case WM_ACTIVATEAPP: {
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_APP_ACTIVATION;
e.data.active = !!wp;
z_emit(host.app, &e);
@ -561,7 +561,7 @@ static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
w->tracking = TRUE;
emit(w, UHDK_EVENT_POINTER_ENTER);
}
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_POINTER_MOVE;
e.window = w->handle;
e.data.pointer.position =
@ -593,7 +593,7 @@ static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
msg == WM_XBUTTONUP;
if (!up)
SetFocus(hwnd);
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_POINTER_BUTTON;
e.window = w->handle;
e.data.pointer.position =
@ -613,7 +613,7 @@ static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
return 0;
}
case WM_CAPTURECHANGED: {
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_POINTER_MODE_CHANGED;
e.window = w->handle;
e.data.pointer_mode = UHDK_POINTER_NORMAL;
@ -628,7 +628,7 @@ static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
case WM_MOUSEHWHEEL: {
POINT p = {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)};
ScreenToClient(hwnd, &p);
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_SCROLL;
e.window = w->handle;
e.data.scroll.position = (uhdk_point){p.x / w->scale, p.y / w->scale};
@ -663,7 +663,7 @@ static LRESULT CALLBACK windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
return 0;
}
}
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_KEY;
e.window = w->handle;
e.input_id = w->input_id = ++host.input_id;
@ -1194,7 +1194,7 @@ static void showDialog(void) {
} else
status = files ? UHDK_STATUS_OS_ERROR : UHDK_STATUS_OUT_OF_MEMORY;
}
uhdk_request_result result = {0};
z_request_result result = {0};
result.value.files = (uhdk_file_list){files, count};
host.dialog = NULL;
host.dialog_id = 0;
@ -1279,7 +1279,7 @@ static uhdk_status createWatch(uhdk_file *root, uhdk_watch *handle) {
return UHDK_STATUS_OK;
}
static void watchEvent(Watch *watch, uint32_t flags, uhdk_status status) {
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_WATCH;
e.data.watch.watch = watch->handle;
e.data.watch.flags = flags;
@ -1402,7 +1402,7 @@ void z_native_run(void) {
while (host.running) {
int result = GetMessageW(&message, NULL, 0, 0);
if (result <= 0) {
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_QUIT_REQUESTED;
z_emit(host.app, &e);
if (result < 0)
@ -1578,7 +1578,7 @@ static uhdk_status clipboardRead(uint64_t id, uint64_t limit) {
profileFree(text);
return UHDK_STATUS_BUFFER_TOO_SMALL;
}
uhdk_request_result result = {0};
z_request_result result = {0};
result.value.text = (uhdk_text){text, len};
z_complete(host.app, id, UHDK_STATUS_OK, &result);
profileFree(text);
@ -1632,7 +1632,7 @@ static uhdk_status setIcon(const uhdk_image *image) {
return UHDK_STATUS_OK;
}
uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t request,
uhdk_request_result *result) {
z_request_result *result) {
(void)app;
Window *w =
cmd->target && z_object_kind(cmd->target) == Z_KIND_WINDOW ? windowOf(cmd->target) : NULL;
@ -1765,7 +1765,7 @@ uhdk_status z_native_execute(uhdk_app *app, const z_command *cmd, uint64_t reque
ReleaseCapture();
break;
}
uhdk_event e = {0};
z_event e = {0};
e.type = UHDK_EVENT_POINTER_MODE_CHANGED;
e.window = w->handle;
e.data.pointer_mode = (uint32_t)cmd->number;