diff options
Diffstat (limited to 'src/bun.js/api')
| -rw-r--r-- | src/bun.js/api/bun.zig | 142 |
1 files changed, 111 insertions, 31 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 4dcbad374..78927f85a 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -2777,12 +2777,19 @@ pub const Timer = struct { warned: bool = false, // We split up the map here to avoid storing an extra "repeat" boolean - - /// Used by setTimeout() - timeout_map: TimeoutMap = TimeoutMap{}, - - /// Used by setInterval() - interval_map: TimeoutMap = TimeoutMap{}, + maps: struct { + setTimeout: TimeoutMap = .{}, + setInterval: TimeoutMap = .{}, + setImmediate: TimeoutMap = .{}, + + pub inline fn get(this: *@This(), kind: Timeout.Kind) *TimeoutMap { + return switch (kind) { + .setTimeout => &this.setTimeout, + .setInterval => &this.setInterval, + .setImmediate => &this.setImmediate, + }; + } + } = .{}, /// TimeoutMap is map of i32 to nullable Timeout structs /// i32 is exposed to JavaScript and can be used with clearTimeout, clearInterval, etc. @@ -2812,7 +2819,7 @@ pub const Timer = struct { globalThis: *JSC.JSGlobalObject, callback: JSC.Strong = .{}, arguments: JSC.Strong = .{}, - repeat: bool = false, + kind: Timeout.Kind = .setTimeout, pub const Task = JSC.AnyTask.New(CallbackJob, perform); @@ -2849,12 +2856,13 @@ pub const Timer = struct { pub fn perform(this: *CallbackJob) void { var globalThis = this.globalThis; var vm = globalThis.bunVM(); - var map: *TimeoutMap = if (this.repeat) &vm.timer.interval_map else &vm.timer.timeout_map; + const kind = this.kind; + var map: *TimeoutMap = vm.timer.maps.get(kind); // This doesn't deinit the timer // Timers are deinit'd separately // We do need to handle when the timer is cancelled after the job has been enqueued - if (!this.repeat) { + if (kind != .setInterval) { if (map.fetchSwapRemove(this.id) == null) { // if the timeout was cancelled, don't run the callback this.deinit(); @@ -2934,6 +2942,47 @@ pub const Timer = struct { } }; + pub const TimerObject = struct { + id: i32 = -1, + kind: Timeout.Kind = .setTimeout, + ref_count: u16 = 1, + + pub usingnamespace JSC.Codegen.JSTimeout; + + pub fn doRef(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + if (this.ref_count > 0) + this.ref_count +|= 1; + + return JSValue.jsUndefined(); + } + + pub fn doUnref(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + this.ref_count -|= 1; + if (this.ref_count == 0) { + switch (this.kind) { + .setTimeout, .setImmediate => { + _ = clearTimeout(globalObject, JSValue.jsNumber(this.id)); + }, + .setInterval => { + _ = clearInterval(globalObject, JSValue.jsNumber(this.id)); + }, + } + } + + return JSValue.jsUndefined(); + } + pub fn hasRef(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + return JSValue.jsBoolean(this.ref_count > 0 and globalObject.bunVM().timer.maps.get(this.kind).contains(this.id)); + } + pub fn toPrimitive(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { + return JSValue.jsNumber(this.id); + } + + pub fn finalize(this: *TimerObject) callconv(.C) void { + bun.default_allocator.destroy(this); + } + }; + pub const Timeout = struct { callback: JSC.Strong = .{}, globalThis: *JSC.JSGlobalObject, @@ -2941,11 +2990,21 @@ pub const Timer = struct { poll_ref: JSC.PollRef = JSC.PollRef.init(), arguments: JSC.Strong = .{}, + pub const Kind = enum(u32) { + setTimeout, + setInterval, + setImmediate, + }; + // this is sized to be the same as one pointer pub const ID = extern struct { id: i32, - repeat: u32 = 0, + kind: Kind = Kind.setTimeout, + + pub fn repeats(this: ID) bool { + return this.kind == .setInterval; + } }; pub fn run(timer: *uws.Timer) callconv(.C) void { @@ -2955,9 +3014,9 @@ pub const Timer = struct { // to handle the timeout being cancelled after already enqueued var vm = JSC.VirtualMachine.get(); - const repeats = timer_id.repeat > 0; + const repeats = timer_id.repeats(); - var map = if (repeats) &vm.timer.interval_map else &vm.timer.timeout_map; + var map = vm.timer.maps.get(timer_id.kind); var this_: ?Timeout = map.get( timer_id.id, @@ -3000,7 +3059,7 @@ pub const Timer = struct { this.arguments, .globalThis = globalThis, .id = timer_id.id, - .repeat = timer_id.repeat > 0, + .kind = timer_id.kind, }; // This allows us to: @@ -3054,19 +3113,18 @@ pub const Timer = struct { if (repeat) @as(i32, 1) else 0, ); - var map = if (repeat) - &vm.timer.interval_map - else - &vm.timer.timeout_map; + const kind: Timeout.Kind = if (repeat) .setInterval else .setTimeout; + + var map = vm.timer.maps.get(kind); // setImmediate(foo) // setTimeout(foo, 0) - if (interval == 0) { + if (kind == .setTimeout and interval == 0) { var cb: CallbackJob = .{ .callback = JSC.Strong.create(callback, globalThis), .globalThis = globalThis, .id = id, - .repeat = false, + .kind = kind, }; if (arguments_array_or_zero != .zero) { @@ -3093,7 +3151,7 @@ pub const Timer = struct { vm.uws_event_loop.?, Timeout.ID{ .id = id, - .repeat = @as(u32, @boolToInt(repeat)), + .kind = kind, }, ), }; @@ -3108,11 +3166,11 @@ pub const Timer = struct { timeout.timer.set( Timeout.ID{ .id = id, - .repeat = if (repeat) 1 else 0, + .kind = kind, }, Timeout.run, interval, - @as(i32, @boolToInt(repeat)) * interval, + @as(i32, @boolToInt(kind == .setInterval)) * interval, ); } @@ -3129,7 +3187,13 @@ pub const Timer = struct { Timer.set(id, globalThis, callback, countdown, arguments, false) catch return JSValue.jsUndefined(); - return JSValue.jsNumberWithType(i32, id); + var timer = globalThis.allocator().create(TimerObject) catch unreachable; + timer.* = .{ + .id = id, + .kind = .setTimeout, + }; + + return timer.toJS(globalThis); } pub fn setInterval( globalThis: *JSGlobalObject, @@ -3144,21 +3208,37 @@ pub const Timer = struct { Timer.set(id, globalThis, callback, countdown, arguments, true) catch return JSValue.jsUndefined(); - return JSValue.jsNumberWithType(i32, id); + var timer = globalThis.allocator().create(TimerObject) catch unreachable; + timer.* = .{ + .id = id, + .kind = .setInterval, + }; + + return timer.toJS(globalThis); } - pub fn clearTimer(timer_id: JSValue, globalThis: *JSGlobalObject, repeats: bool) void { + pub fn clearTimer(timer_id_value: JSValue, globalThis: *JSGlobalObject, repeats: bool) void { JSC.markBinding(@src()); - var map = if (repeats) &VirtualMachine.get().timer.interval_map else &VirtualMachine.get().timer.timeout_map; - if (!timer_id.isAnyInt()) { - return; - } + const kind: Timeout.Kind = if (repeats) .setInterval else .setTimeout; + + var map = globalThis.bunVM().timer.maps.get(kind); const id: Timeout.ID = .{ - .id = timer_id.coerce(i32, globalThis), - .repeat = @as(u32, @boolToInt(repeats)), + .id = brk: { + if (timer_id_value.isAnyInt()) { + break :brk timer_id_value.coerce(i32, globalThis); + } + + if (TimerObject.fromJS(timer_id_value)) |timer_obj| { + break :brk timer_obj.id; + } + + return; + }, + .kind = kind, }; + var timer = map.fetchSwapRemove(id.id) orelse return; if (timer.value == null) { // this timer was scheduled to run but was cancelled before it was run |
