Preserve READY ordering after successful native initialization

Reserve the lifecycle event before startup and publish it first only after startup succeeds. This keeps initial macOS and Windows preferences behind READY while failed Wayland initialization emits no READY. Verified 18 core tests, Plasma native lifecycle/pixel tests, and Windows compilation.
This commit is contained in:
Peterino2 2026-09-18 13:19:55 -07:00
parent 685985e9cd
commit 7a7a3493d6
3 changed files with 45 additions and 2 deletions

View File

@ -59,9 +59,13 @@ pub export fn uhdk_app_run(
const application = runtime.fromAppHandle(handle);
if (application.has_run) return abi.UNAVAILABLE;
profile.thread("UHDK platform");
const ready = runtime.prepareReady(application) catch return abi.OOM;
const status = c.z_native_start(handle, abi.textSpan(application.name));
if (status != abi.OK) return status;
runtime.simpleEvent(application, c.UHDK_EVENT_READY);
if (status != abi.OK) {
runtime.destroyEvent(ready);
return status;
}
runtime.publishReady(application, ready);
application.entry = entry;
application.user = user;
application.running = true;

View File

@ -164,3 +164,21 @@ test "poll allocation failure keeps queued terminal completion retryable" {
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);
}
test "READY precedes native startup events without being published on failed startup" {
const application = try Application.create(std.testing.allocator, std.testing.io, "startup");
defer destroyApplication(application);
const failed = try runtime.prepareReady(application);
try std.testing.expect(application.pending_head == null);
runtime.destroyEvent(failed);
const ready = try runtime.prepareReady(application);
runtime.simpleEvent(application, c.UHDK_EVENT_PREFERENCES_CHANGED);
const preferences = application.pending_head.?;
runtime.publishReady(application, ready);
runtime.simpleEvent(application, c.UHDK_EVENT_WAKE);
try std.testing.expectEqual(@as(u32, c.UHDK_EVENT_READY), application.pending_head.?.event.type);
try std.testing.expect(ready.next == preferences);
try std.testing.expect(ready.event.time_ns <= preferences.event.time_ns);
try std.testing.expectEqual(@as(u32, c.UHDK_EVENT_WAKE), preferences.next.?.event.type);
try std.testing.expect(application.pending_tail == preferences.next);
}

View File

@ -152,6 +152,27 @@ pub fn copyPayload(node: *Event) !void {
}
}
// Reserve READY before native initialization, but expose it only after startup
// succeeds. Initial preferences/open-file events may arrive during that call.
pub fn prepareReady(application: *Application) !*Event {
const node = try application.gpa.create(Event);
node.* = .{ .gpa = application.gpa, .arena = .init(application.gpa) };
node.event.type = c.UHDK_EVENT_READY;
node.event.struct_size = @sizeOf(c.z_event);
node.event.time_ns = now(application.io) - application.epoch;
return node;
}
pub fn publishReady(application: *Application, node: *Event) void {
std.debug.assert(node.event.type == c.UHDK_EVENT_READY);
application.lock();
defer application.unlock();
node.next = application.pending_head;
application.pending_head = node;
if (application.pending_tail == null) application.pending_tail = node;
application.notify();
}
pub fn simpleEvent(application: *Application, typ: u32) void {
var event = std.mem.zeroes(c.z_event);
event.type = typ;