new header parser and generator
This commit is contained in:
parent
97c4b41e12
commit
8a1f8fbd28
|
|
@ -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.
|
||||
|
||||
55
README.md
55
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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", .{
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
543
include/uhdk.h
543
include/uhdk.h
|
|
@ -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,86 +887,119 @@ enum {
|
|||
UHDK_EDIT_TAB
|
||||
};
|
||||
|
||||
/* 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;
|
||||
} 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;
|
||||
} 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;
|
||||
} uhdk_scroll_event;
|
||||
|
||||
typedef struct uhdk_text_event {
|
||||
uhdk_text text;
|
||||
uint64_t revision;
|
||||
uhdk_bool has_replacement;
|
||||
uint32_t reserved;
|
||||
uint64_t replacement_start;
|
||||
uint64_t replacement_len;
|
||||
uint64_t selection_start;
|
||||
uint64_t selection_len; /* Within preedit text. */
|
||||
} uhdk_text_event;
|
||||
|
||||
typedef struct uhdk_edit_event {
|
||||
uhdk_edit_command command;
|
||||
uhdk_bool extend_selection;
|
||||
} uhdk_edit_event;
|
||||
|
||||
typedef struct uhdk_drop_files_event {
|
||||
uhdk_file_list files;
|
||||
uhdk_point position;
|
||||
} uhdk_drop_files_event;
|
||||
|
||||
typedef struct uhdk_menu_event {
|
||||
uint64_t command_id;
|
||||
} 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. */
|
||||
} 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. */
|
||||
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. */
|
||||
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;
|
||||
union {
|
||||
uhdk_bool active;
|
||||
uhdk_file_list files;
|
||||
uhdk_window_metrics metrics;
|
||||
uhdk_preferences preferences;
|
||||
struct {
|
||||
uint32_t physical_key;
|
||||
uhdk_key logical_key;
|
||||
uhdk_modifier_flags modifiers;
|
||||
uhdk_bool pressed;
|
||||
uhdk_bool repeat;
|
||||
} key;
|
||||
struct {
|
||||
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_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_text text;
|
||||
uint64_t revision;
|
||||
uhdk_bool has_replacement;
|
||||
uint32_t reserved;
|
||||
uint64_t replacement_start;
|
||||
uint64_t replacement_len;
|
||||
uint64_t selection_start;
|
||||
uint64_t selection_len; /* Within preedit text. */
|
||||
} text;
|
||||
struct {
|
||||
uhdk_edit_command command;
|
||||
uhdk_bool extend_selection;
|
||||
} edit;
|
||||
struct {
|
||||
uhdk_file_list files;
|
||||
uhdk_point position;
|
||||
} drop_files;
|
||||
struct {
|
||||
uint64_t command_id;
|
||||
} menu;
|
||||
struct {
|
||||
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;
|
||||
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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
49
src/native.h
49
src/native.h
|
|
@ -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 *);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue