mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Merge pull request #600 from lightpanda-io/timeouts_and_intervals
Make intervals easier and faster, add window.setInterval and clearInt…
This commit is contained in:
@@ -32,6 +32,7 @@ const Walker = @import("dom/walker.zig").WalkerDepthFirst;
|
||||
|
||||
const Env = @import("env.zig").Env;
|
||||
const App = @import("../app.zig").App;
|
||||
const Loop = @import("../runtime/loop.zig").Loop;
|
||||
|
||||
const URL = @import("../url.zig").URL;
|
||||
|
||||
@@ -171,8 +172,7 @@ pub const Session = struct {
|
||||
|
||||
std.debug.assert(self.page != null);
|
||||
// Reset all existing callbacks.
|
||||
self.browser.app.loop.resetJS();
|
||||
self.browser.app.loop.resetZig();
|
||||
self.browser.app.loop.reset();
|
||||
self.executor.endScope();
|
||||
self.page = null;
|
||||
|
||||
@@ -230,6 +230,8 @@ pub const Page = struct {
|
||||
|
||||
renderer: FlatRenderer,
|
||||
|
||||
microtask_node: Loop.CallbackNode,
|
||||
|
||||
window_clicked_event_node: parser.EventNode,
|
||||
|
||||
scope: *Env.Scope,
|
||||
@@ -248,6 +250,7 @@ pub const Page = struct {
|
||||
.url = URL.empty,
|
||||
.session = session,
|
||||
.renderer = FlatRenderer.init(arena),
|
||||
.microtask_node = .{ .func = microtaskCallback },
|
||||
.window_clicked_event_node = .{ .func = windowClicked },
|
||||
.state = .{
|
||||
.arena = arena,
|
||||
@@ -264,13 +267,13 @@ pub const Page = struct {
|
||||
// load polyfills
|
||||
try polyfill.load(self.arena, self.scope);
|
||||
|
||||
self.microtaskLoop();
|
||||
// _ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
|
||||
}
|
||||
|
||||
fn microtaskLoop(self: *Page) void {
|
||||
const browser = self.session.browser;
|
||||
browser.runMicrotasks();
|
||||
browser.app.loop.zigTimeout(1 * std.time.ns_per_ms, *Page, self, microtaskLoop);
|
||||
fn microtaskCallback(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
|
||||
const self: *Page = @fieldParentPtr("microtask_node", node);
|
||||
self.session.browser.runMicrotasks();
|
||||
repeat_delay.* = 1 * std.time.ns_per_ms;
|
||||
}
|
||||
|
||||
// dump writes the page content into the given file.
|
||||
@@ -297,20 +300,19 @@ pub const Page = struct {
|
||||
}
|
||||
|
||||
pub fn wait(self: *Page) !void {
|
||||
// try catch
|
||||
var try_catch: Env.TryCatch = undefined;
|
||||
try_catch.init(self.scope);
|
||||
defer try_catch.deinit();
|
||||
|
||||
self.session.browser.app.loop.run() catch |err| {
|
||||
if (try try_catch.err(self.arena)) |msg| {
|
||||
log.info("wait error: {s}", .{msg});
|
||||
return;
|
||||
} else {
|
||||
log.info("wait error: {any}", .{err});
|
||||
}
|
||||
};
|
||||
try self.session.browser.app.loop.run();
|
||||
|
||||
if (try_catch.hasCaught() == false) {
|
||||
log.debug("wait: OK", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
const msg = (try try_catch.err(self.arena)) orelse "unknown";
|
||||
log.info("wait error: {s}", .{msg});
|
||||
}
|
||||
|
||||
pub fn origin(self: *const Page, arena: Allocator) ![]const u8 {
|
||||
|
||||
@@ -21,6 +21,7 @@ const std = @import("std");
|
||||
const parser = @import("../netsurf.zig");
|
||||
const Callback = @import("../env.zig").Callback;
|
||||
const SessionState = @import("../env.zig").SessionState;
|
||||
const Loop = @import("../../runtime/loop.zig").Loop;
|
||||
|
||||
const Navigator = @import("navigator.zig").Navigator;
|
||||
const History = @import("history.zig").History;
|
||||
@@ -31,6 +32,8 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||
|
||||
const storage = @import("../storage/storage.zig");
|
||||
|
||||
const log = std.log.scoped(.window);
|
||||
|
||||
// https://dom.spec.whatwg.org/#interface-window-extensions
|
||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
||||
pub const Window = struct {
|
||||
@@ -45,10 +48,9 @@ pub const Window = struct {
|
||||
location: Location = .{},
|
||||
storage_shelf: ?*storage.Shelf = null,
|
||||
|
||||
// store a map between internal timeouts ids and pointers to uint.
|
||||
// the maximum number of possible timeouts is fixed.
|
||||
timeoutid: u32 = 0,
|
||||
timeoutids: [512]u64 = undefined,
|
||||
// counter for having unique timer ids
|
||||
timer_id: u31 = 0,
|
||||
timers: std.AutoHashMapUnmanaged(u32, *TimerCallback) = .{},
|
||||
|
||||
crypto: Crypto = .{},
|
||||
console: Console = .{},
|
||||
@@ -129,23 +131,93 @@ pub const Window = struct {
|
||||
|
||||
// TODO handle callback arguments.
|
||||
pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
|
||||
if (self.timeoutid >= self.timeoutids.len) return error.TooMuchTimeout;
|
||||
return self.createTimeout(cbk, delay, state, false);
|
||||
}
|
||||
|
||||
const ddelay: u63 = delay orelse 0;
|
||||
const id = try state.loop.timeout(ddelay * std.time.ns_per_ms, cbk);
|
||||
|
||||
self.timeoutids[self.timeoutid] = id;
|
||||
defer self.timeoutid += 1;
|
||||
|
||||
return self.timeoutid;
|
||||
// TODO handle callback arguments.
|
||||
pub fn _setInterval(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
|
||||
return self.createTimeout(cbk, delay, state, true);
|
||||
}
|
||||
|
||||
pub fn _clearTimeout(self: *Window, id: u32, state: *SessionState) !void {
|
||||
// I do would prefer return an error in this case, but it seems some JS
|
||||
// uses invalid id, in particular id 0.
|
||||
// So we silently ignore invalid id for now.
|
||||
if (id >= self.timeoutid) return;
|
||||
const kv = self.timers.fetchRemove(id) orelse return;
|
||||
try state.loop.cancel(kv.value.loop_id);
|
||||
}
|
||||
|
||||
try state.loop.cancel(self.timeoutids[id], null);
|
||||
pub fn _clearInterval(self: *Window, id: u32, state: *SessionState) !void {
|
||||
const kv = self.timers.fetchRemove(id) orelse return;
|
||||
try state.loop.cancel(kv.value.loop_id);
|
||||
}
|
||||
|
||||
pub fn createTimeout(self: *Window, cbk: Callback, delay_: ?u32, state: *SessionState, comptime repeat: bool) !u32 {
|
||||
if (self.timers.count() > 512) {
|
||||
return error.TooManyTimeout;
|
||||
}
|
||||
const timer_id = self.timer_id +% 1;
|
||||
self.timer_id = timer_id;
|
||||
|
||||
const arena = state.arena;
|
||||
|
||||
const gop = try self.timers.getOrPut(arena, timer_id);
|
||||
if (gop.found_existing) {
|
||||
// this can only happen if we've created 2^31 timeouts.
|
||||
return error.TooManyTimeout;
|
||||
}
|
||||
errdefer _ = self.timers.remove(timer_id);
|
||||
|
||||
const delay: u63 = (delay_ orelse 0) * std.time.ns_per_ms;
|
||||
const callback = try arena.create(TimerCallback);
|
||||
|
||||
callback.* = .{
|
||||
.cbk = cbk,
|
||||
.loop_id = 0, // we're going to set this to a real value shortly
|
||||
.window = self,
|
||||
.timer_id = timer_id,
|
||||
.node = .{ .func = TimerCallback.run },
|
||||
.repeat = if (repeat) delay else null,
|
||||
};
|
||||
callback.loop_id = try state.loop.timeout(delay, &callback.node);
|
||||
|
||||
gop.value_ptr.* = callback;
|
||||
return timer_id;
|
||||
}
|
||||
};
|
||||
|
||||
const TimerCallback = struct {
|
||||
// the internal loop id, need it when cancelling
|
||||
loop_id: usize,
|
||||
|
||||
// the id of our timer (windows.timers key)
|
||||
timer_id: u31,
|
||||
|
||||
// The JavaScript callback to execute
|
||||
cbk: Callback,
|
||||
|
||||
// This is the internal data that the event loop tracks. We'll get this
|
||||
// back in run and, from it, can get our TimerCallback instance
|
||||
node: Loop.CallbackNode = undefined,
|
||||
|
||||
// if the event should be repeated
|
||||
repeat: ?u63 = null,
|
||||
|
||||
window: *Window,
|
||||
|
||||
fn run(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
|
||||
const self: *TimerCallback = @fieldParentPtr("node", node);
|
||||
|
||||
var result: Callback.Result = undefined;
|
||||
self.cbk.tryCall(.{}, &result) catch {
|
||||
log.err("timeout callback error: {s}", .{result.exception});
|
||||
log.debug("stack:\n{s}", .{result.stack orelse "???"});
|
||||
};
|
||||
|
||||
if (self.repeat) |r| {
|
||||
// setInterval
|
||||
repeat_delay.* = r;
|
||||
return;
|
||||
}
|
||||
|
||||
// setTimeout
|
||||
_ = self.window.timers.remove(self.timer_id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -156,12 +156,11 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
|
||||
var try_catch: Env.TryCatch = undefined;
|
||||
try_catch.init(runner.scope);
|
||||
defer try_catch.deinit();
|
||||
runner.loop.run() catch |err| {
|
||||
if (try try_catch.err(arena)) |msg| {
|
||||
err_out.* = msg;
|
||||
try runner.loop.run();
|
||||
|
||||
if (try_catch.hasCaught()) {
|
||||
err_out.* = (try try_catch.err(arena)) orelse "unknwon error";
|
||||
}
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
// Check the final test status.
|
||||
|
||||
@@ -37,24 +37,15 @@ pub const Loop = struct {
|
||||
alloc: std.mem.Allocator, // TODO: unmanaged version ?
|
||||
io: IO,
|
||||
|
||||
// both events_nb are used to track how many callbacks are to be called.
|
||||
// We use these counters to wait until all the events are finished.
|
||||
js_events_nb: usize,
|
||||
zig_events_nb: usize,
|
||||
// Used to track how many callbacks are to be called and wait until all
|
||||
// event are finished.
|
||||
events_nb: usize,
|
||||
|
||||
cbk_error: bool = false,
|
||||
|
||||
// js_ctx_id is incremented each time the loop is reset for JS.
|
||||
// All JS callbacks store an initial js_ctx_id and compare before execution.
|
||||
// ctx_id is incremented each time the loop is reset.
|
||||
// All callbacks store an initial ctx_id and compare before execution.
|
||||
// If a ctx is outdated, the callback is ignored.
|
||||
// This is a weak way to cancel all future JS callbacks.
|
||||
js_ctx_id: u32 = 0,
|
||||
|
||||
// zig_ctx_id is incremented each time the loop is reset for Zig.
|
||||
// All Zig callbacks store an initial zig_ctx_id and compare before execution.
|
||||
// If a ctx is outdated, the callback is ignored.
|
||||
// This is a weak way to cancel all future Zig callbacks.
|
||||
zig_ctx_id: u32 = 0,
|
||||
// This is a weak way to cancel all future callbacks.
|
||||
ctx_id: u32 = 0,
|
||||
|
||||
// We use this to track cancellation ids and, on the timeout callback,
|
||||
// we can can check here to see if it's been cancelled.
|
||||
@@ -66,32 +57,27 @@ pub const Loop = struct {
|
||||
const Self = @This();
|
||||
pub const Completion = IO.Completion;
|
||||
|
||||
pub const ConnectError = IO.ConnectError;
|
||||
pub const RecvError = IO.RecvError;
|
||||
pub const SendError = IO.SendError;
|
||||
pub const ConnectError = IO.ConnectError;
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator) !Self {
|
||||
return Self{
|
||||
.alloc = alloc,
|
||||
.cancelled = .{},
|
||||
.io = try IO.init(32, 0),
|
||||
.js_events_nb = 0,
|
||||
.zig_events_nb = 0,
|
||||
.events_nb = 0,
|
||||
.timeout_pool = MemoryPool(ContextTimeout).init(alloc),
|
||||
.event_callback_pool = MemoryPool(EventCallbackContext).init(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
// first disable callbacks for existing events.
|
||||
// We don't want a callback re-create a setTimeout, it could create an
|
||||
// infinite loop on wait for events.
|
||||
self.resetJS();
|
||||
self.resetZig();
|
||||
self.reset();
|
||||
|
||||
// run tail events. We do run the tail events to ensure all the
|
||||
// contexts are correcly free.
|
||||
while (self.eventsNb(.js) > 0 or self.eventsNb(.zig) > 0) {
|
||||
while (self.eventsNb() > 0) {
|
||||
self.io.run_for_ns(10 * std.time.ns_per_ms) catch |err| {
|
||||
log.err("deinit run tail events: {any}", .{err});
|
||||
break;
|
||||
@@ -112,40 +98,24 @@ pub const Loop = struct {
|
||||
// Note that I/O events callbacks might register more I/O events
|
||||
// on the go when they are executed (ie. nested I/O events).
|
||||
pub fn run(self: *Self) !void {
|
||||
while (self.eventsNb(.js) > 0) {
|
||||
while (self.eventsNb() > 0) {
|
||||
try self.io.run_for_ns(10 * std.time.ns_per_ms);
|
||||
// at each iteration we might have new events registred by previous callbacks
|
||||
}
|
||||
// TODO: return instead immediatly on the first JS callback error
|
||||
// and let the caller decide what to do next
|
||||
// (typically retrieve the exception through the TryCatch and
|
||||
// continue the execution of callbacks with a new call to loop.run)
|
||||
if (self.cbk_error) {
|
||||
return error.JSExecCallback;
|
||||
}
|
||||
}
|
||||
|
||||
const Event = enum { js, zig };
|
||||
|
||||
fn eventsPtr(self: *Self, comptime event: Event) *usize {
|
||||
return switch (event) {
|
||||
.zig => &self.zig_events_nb,
|
||||
.js => &self.js_events_nb,
|
||||
};
|
||||
}
|
||||
|
||||
// Register events atomically
|
||||
// - add 1 event and return previous value
|
||||
fn addEvent(self: *Self, comptime event: Event) void {
|
||||
_ = @atomicRmw(usize, self.eventsPtr(event), .Add, 1, .acq_rel);
|
||||
fn addEvent(self: *Self) void {
|
||||
_ = @atomicRmw(usize, &self.events_nb, .Add, 1, .acq_rel);
|
||||
}
|
||||
// - remove 1 event and return previous value
|
||||
fn removeEvent(self: *Self, comptime event: Event) void {
|
||||
_ = @atomicRmw(usize, self.eventsPtr(event), .Sub, 1, .acq_rel);
|
||||
fn removeEvent(self: *Self) void {
|
||||
_ = @atomicRmw(usize, &self.events_nb, .Sub, 1, .acq_rel);
|
||||
}
|
||||
// - get the number of current events
|
||||
fn eventsNb(self: *Self, comptime event: Event) usize {
|
||||
return @atomicLoad(usize, self.eventsPtr(event), .seq_cst);
|
||||
fn eventsNb(self: *Self) usize {
|
||||
return @atomicLoad(usize, &self.events_nb, .seq_cst);
|
||||
}
|
||||
|
||||
// JS callbacks APIs
|
||||
@@ -153,10 +123,18 @@ pub const Loop = struct {
|
||||
|
||||
// Timeout
|
||||
|
||||
// The state that we add to a timeout. This is what we get back from a
|
||||
// timeoutCallback. It contains the function to execute. The user is expected
|
||||
// to be able to turn a reference to this into whatever state it needs,
|
||||
// probably by inserting this node into its own stae and using @fieldParentPtr
|
||||
pub const CallbackNode = struct {
|
||||
func: *const fn (node: *CallbackNode, repeat: *?u63) void,
|
||||
};
|
||||
|
||||
const ContextTimeout = struct {
|
||||
loop: *Self,
|
||||
js_cbk: ?JSCallback,
|
||||
js_ctx_id: u32,
|
||||
ctx_id: u32,
|
||||
callback_node: ?*CallbackNode,
|
||||
};
|
||||
|
||||
fn timeoutCallback(
|
||||
@@ -164,21 +142,25 @@ pub const Loop = struct {
|
||||
completion: *IO.Completion,
|
||||
result: IO.TimeoutError!void,
|
||||
) void {
|
||||
var repeating = false;
|
||||
const loop = ctx.loop;
|
||||
|
||||
defer {
|
||||
loop.removeEvent(.js);
|
||||
loop.removeEvent();
|
||||
if (repeating == false) {
|
||||
loop.timeout_pool.destroy(ctx);
|
||||
loop.alloc.destroy(completion);
|
||||
}
|
||||
}
|
||||
|
||||
if (loop.cancelled.remove(@intFromPtr(completion))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the loop's context id has changed, don't call the js callback
|
||||
// function. The callback's memory has already be cleaned and the
|
||||
// events nb reset.
|
||||
if (ctx.js_ctx_id != loop.js_ctx_id) return;
|
||||
// Abort if this completion was created for a different version of the loop.
|
||||
if (ctx.ctx_id != loop.ctx_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: return the error to the callback
|
||||
result catch |err| {
|
||||
@@ -189,56 +171,51 @@ pub const Loop = struct {
|
||||
return;
|
||||
};
|
||||
|
||||
// js callback
|
||||
if (ctx.js_cbk) |*js_cbk| {
|
||||
js_cbk.call(null) catch {
|
||||
loop.cbk_error = true;
|
||||
};
|
||||
if (ctx.callback_node) |cn| {
|
||||
var repeat_in: ?u63 = null;
|
||||
cn.func(cn, &repeat_in);
|
||||
if (repeat_in) |r| {
|
||||
// prevents our context and completion from being cleaned up
|
||||
repeating = true;
|
||||
loop.scheduleTimeout(r, ctx, completion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timeout(self: *Self, nanoseconds: u63, js_cbk: ?JSCallback) !usize {
|
||||
pub fn timeout(self: *Self, nanoseconds: u63, callback_node: ?*CallbackNode) !usize {
|
||||
const completion = try self.alloc.create(Completion);
|
||||
errdefer self.alloc.destroy(completion);
|
||||
completion.* = undefined;
|
||||
|
||||
const ctx = try self.timeout_pool.create();
|
||||
errdefer self.timeout_pool.destroy(ctx);
|
||||
ctx.* = ContextTimeout{
|
||||
ctx.* = .{
|
||||
.loop = self,
|
||||
.js_cbk = js_cbk,
|
||||
.js_ctx_id = self.js_ctx_id,
|
||||
.ctx_id = self.ctx_id,
|
||||
.callback_node = callback_node,
|
||||
};
|
||||
|
||||
self.addEvent(.js);
|
||||
self.io.timeout(*ContextTimeout, ctx, timeoutCallback, completion, nanoseconds);
|
||||
self.scheduleTimeout(nanoseconds, ctx, completion);
|
||||
return @intFromPtr(completion);
|
||||
}
|
||||
|
||||
pub fn cancel(self: *Self, id: usize, js_cbk: ?JSCallback) !void {
|
||||
fn scheduleTimeout(self: *Self, nanoseconds: u63, ctx: *ContextTimeout, completion: *Completion) void {
|
||||
self.addEvent();
|
||||
self.io.timeout(*ContextTimeout, ctx, timeoutCallback, completion, nanoseconds);
|
||||
}
|
||||
|
||||
pub fn cancel(self: *Self, id: usize) !void {
|
||||
try self.cancelled.put(self.alloc, id, {});
|
||||
if (js_cbk) |cbk| {
|
||||
cbk.call(null) catch {
|
||||
self.cbk_error = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all existing JS callbacks.
|
||||
// Reset all existing callbacks.
|
||||
// The existing events will happen and their memory will be cleanup but the
|
||||
// corresponding callbacks will not be called.
|
||||
pub fn resetJS(self: *Self) void {
|
||||
self.js_ctx_id += 1;
|
||||
pub fn reset(self: *Self) void {
|
||||
self.ctx_id += 1;
|
||||
self.cancelled.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
// Reset all existing Zig callbacks.
|
||||
// The existing events will happen and their memory will be cleanup but the
|
||||
// corresponding callbacks will not be called.
|
||||
pub fn resetZig(self: *Self) void {
|
||||
self.zig_ctx_id += 1;
|
||||
}
|
||||
|
||||
// IO callbacks APIs
|
||||
// -----------------
|
||||
|
||||
@@ -256,7 +233,7 @@ pub const Loop = struct {
|
||||
const onConnect = struct {
|
||||
fn onConnect(callback: *EventCallbackContext, completion_: *Completion, res: ConnectError!void) void {
|
||||
defer callback.loop.event_callback_pool.destroy(callback);
|
||||
callback.loop.removeEvent(.js);
|
||||
callback.loop.removeEvent();
|
||||
cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res);
|
||||
}
|
||||
}.onConnect;
|
||||
@@ -265,7 +242,7 @@ pub const Loop = struct {
|
||||
errdefer self.event_callback_pool.destroy(callback);
|
||||
callback.* = .{ .loop = self, .ctx = ctx };
|
||||
|
||||
self.addEvent(.js);
|
||||
self.addEvent();
|
||||
self.io.connect(*EventCallbackContext, callback, onConnect, completion, socket, address);
|
||||
}
|
||||
|
||||
@@ -283,7 +260,7 @@ pub const Loop = struct {
|
||||
const onSend = struct {
|
||||
fn onSend(callback: *EventCallbackContext, completion_: *Completion, res: SendError!usize) void {
|
||||
defer callback.loop.event_callback_pool.destroy(callback);
|
||||
callback.loop.removeEvent(.js);
|
||||
callback.loop.removeEvent();
|
||||
cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res);
|
||||
}
|
||||
}.onSend;
|
||||
@@ -292,7 +269,7 @@ pub const Loop = struct {
|
||||
errdefer self.event_callback_pool.destroy(callback);
|
||||
callback.* = .{ .loop = self, .ctx = ctx };
|
||||
|
||||
self.addEvent(.js);
|
||||
self.addEvent();
|
||||
self.io.send(*EventCallbackContext, callback, onSend, completion, socket, buf);
|
||||
}
|
||||
|
||||
@@ -310,7 +287,7 @@ pub const Loop = struct {
|
||||
const onRecv = struct {
|
||||
fn onRecv(callback: *EventCallbackContext, completion_: *Completion, res: RecvError!usize) void {
|
||||
defer callback.loop.event_callback_pool.destroy(callback);
|
||||
callback.loop.removeEvent(.js);
|
||||
callback.loop.removeEvent();
|
||||
cbk(@alignCast(@ptrCast(callback.ctx)), completion_, res);
|
||||
}
|
||||
}.onRecv;
|
||||
@@ -319,76 +296,9 @@ pub const Loop = struct {
|
||||
errdefer self.event_callback_pool.destroy(callback);
|
||||
callback.* = .{ .loop = self, .ctx = ctx };
|
||||
|
||||
self.addEvent(.js);
|
||||
self.addEvent();
|
||||
self.io.recv(*EventCallbackContext, callback, onRecv, completion, socket, buf);
|
||||
}
|
||||
|
||||
// Zig timeout
|
||||
|
||||
const ContextZigTimeout = struct {
|
||||
loop: *Self,
|
||||
zig_ctx_id: u32,
|
||||
|
||||
context: *anyopaque,
|
||||
callback: *const fn (
|
||||
context: ?*anyopaque,
|
||||
) void,
|
||||
};
|
||||
|
||||
fn zigTimeoutCallback(
|
||||
ctx: *ContextZigTimeout,
|
||||
completion: *IO.Completion,
|
||||
result: IO.TimeoutError!void,
|
||||
) void {
|
||||
const loop = ctx.loop;
|
||||
defer {
|
||||
loop.removeEvent(.zig);
|
||||
loop.alloc.destroy(ctx);
|
||||
loop.alloc.destroy(completion);
|
||||
}
|
||||
|
||||
// If the loop's context id has changed, don't call the js callback
|
||||
// function. The callback's memory has already be cleaned and the
|
||||
// events nb reset.
|
||||
if (ctx.zig_ctx_id != loop.zig_ctx_id) return;
|
||||
|
||||
result catch |err| {
|
||||
switch (err) {
|
||||
error.Canceled => {},
|
||||
else => log.err("zig timeout callback: {any}", .{err}),
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// callback
|
||||
ctx.callback(ctx.context);
|
||||
}
|
||||
|
||||
// zigTimeout performs a timeout but the callback is a zig function.
|
||||
pub fn zigTimeout(
|
||||
self: *Self,
|
||||
nanoseconds: u63,
|
||||
comptime Context: type,
|
||||
context: Context,
|
||||
comptime callback: fn (context: Context) void,
|
||||
) void {
|
||||
const completion = self.alloc.create(IO.Completion) catch unreachable;
|
||||
completion.* = undefined;
|
||||
const ctxtimeout = self.alloc.create(ContextZigTimeout) catch unreachable;
|
||||
ctxtimeout.* = ContextZigTimeout{
|
||||
.loop = self,
|
||||
.zig_ctx_id = self.zig_ctx_id,
|
||||
.context = context,
|
||||
.callback = struct {
|
||||
fn wrapper(ctx: ?*anyopaque) void {
|
||||
callback(@ptrCast(@alignCast(ctx)));
|
||||
}
|
||||
}.wrapper,
|
||||
};
|
||||
|
||||
self.addEvent(.zig);
|
||||
self.io.timeout(*ContextZigTimeout, ctxtimeout, zigTimeoutCallback, completion, nanoseconds);
|
||||
}
|
||||
};
|
||||
|
||||
const EventCallbackContext = struct {
|
||||
|
||||
@@ -26,7 +26,9 @@ pub const allocator = std.testing.allocator;
|
||||
// browser.Env or the browser.SessionState
|
||||
pub fn Runner(comptime State: type, comptime Global: type, comptime types: anytype) type {
|
||||
const AdjustedTypes = if (Global == void) generate.Tuple(.{ types, DefaultGlobal }) else types;
|
||||
const Env = js.Env(State, struct {pub const Interfaces = AdjustedTypes;});
|
||||
const Env = js.Env(State, struct {
|
||||
pub const Interfaces = AdjustedTypes;
|
||||
});
|
||||
|
||||
return struct {
|
||||
env: *Env,
|
||||
|
||||
@@ -1042,9 +1042,6 @@ pub fn run(
|
||||
// - JS callbacks events from scripts
|
||||
while (true) {
|
||||
try loop.io.run_for_ns(10 * std.time.ns_per_ms);
|
||||
if (loop.cbk_error) {
|
||||
log.err("JS error", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user