Skip to content

Commit

Permalink
feat(wasm-api-dom): update/extend types & API
Browse files Browse the repository at this point in the history
- add RAF support
- add Event.value field & input event support
- update/simplify Zig listener handling
  • Loading branch information
postspectacular committed Oct 4, 2022
1 parent 3e13397 commit e563ccc
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 43 deletions.
8 changes: 6 additions & 2 deletions packages/wasm-api-dom/include/api.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Generated by @thi.ng/wasm-api at 2022-10-03T15:17:25.199Z - DO NOT EDIT!
//! Generated by @thi.ng/wasm-api at 2022-10-04T09:31:43.433Z - DO NOT EDIT!

const std = @import("std");

Expand Down Expand Up @@ -55,7 +55,11 @@ pub const Event = struct {
/// Encoded bitmask of currently pressed modifier keys, see `KeyModifier` enum
modifiers: u8 = 0,
/// Value/name of the key pressed
key: [8:0]u8,
key: [15:0]u8,
/// INPUT event only, the value of the targeted input element.
/// The memory is owned by the DOM API and will be freed immediatedly after the
/// event handler has returned.
value: []const u8,

pub fn getKey(self: *const Event) []const u8 {
return self.key[0..std.mem.indexOfSentinel(u8, 0, &self.key)];
Expand Down
100 changes: 65 additions & 35 deletions packages/wasm-api-dom/include/dom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub usingnamespace dom;

/// DOM event listener function
pub const EventListener = *const fn (event: *const dom.Event) void;
/// RAF event listener function
pub const RAFListener = *const fn (time: f64) void;

/// Reserved reference handle for the browser window itself (e.g. used for event targets)
pub const WINDOW: i32 = -1;
Expand All @@ -15,78 +17,78 @@ pub const DOMError = error{
InvalidID,
};

var listeners: std.ArrayList(?EventListener) = undefined;
var nextListenerID: i32 = -1;
var eventListeners: std.ArrayList(?EventListener) = undefined;
var rafListeners: std.ArrayList(?RAFListener) = undefined;

pub fn init(allocator: std.mem.Allocator) anyerror!void {
listeners = std.ArrayList(?EventListener).init(allocator);
eventListeners = std.ArrayList(?EventListener).init(allocator);
rafListeners = std.ArrayList(?RAFListener).init(allocator);
}

pub extern "dom" fn getWindowInfo(desc: *dom.WindowInfo) void;

pub extern "dom" fn createElement(opts: *const dom.CreateElementOpts) i32;

pub extern "dom" fn removeElement(id: i32) bool;
pub extern "dom" fn removeElement(elementID: i32) bool;

pub extern "dom" fn createCanvas(opts: *const dom.CreateCanvasOpts) i32;

pub extern "dom" fn setCanvasSize(id: i32, width: u16, height: u16, dpr: u8) void;
pub extern "dom" fn setCanvasSize(elementID: i32, width: u16, height: u16, dpr: u8) void;

pub extern "dom" fn _setStringAttrib(id: i32, name: [*]const u8, val: [*]const u8) void;
pub extern "dom" fn _setStringAttrib(elementID: i32, name: [*]const u8, val: [*]const u8) void;

pub fn setStringAttrib(id: i32, name: []const u8, val: []const u8) void {
_setStringAttrib(id, name.ptr, val.ptr);
pub fn setStringAttrib(elementID: i32, name: []const u8, val: []const u8) void {
_setStringAttrib(elementID, name.ptr, val.ptr);
}

pub extern "dom" fn _getStringAttrib(id: i32, name: [*]const u8, val: [*]u8, maxBytes: usize) usize;
pub extern "dom" fn _getStringAttrib(elementID: i32, name: [*]const u8, val: [*]u8, maxBytes: usize) usize;

pub fn getStringAttrib(id: i32, name: []const u8, val: []u8) []u8 {
return val[0.._getStringAttrib(id, name.ptr, val.ptr, val.len)];
pub fn getStringAttrib(elementID: i32, name: []const u8, val: []u8) []u8 {
return val[0.._getStringAttrib(elementID, name.ptr, val.ptr, val.len)];
}

pub extern "dom" fn _setNumericAttrib(id: i32, name: [*]const u8, val: f64) void;
pub extern "dom" fn _setNumericAttrib(elementID: i32, name: [*]const u8, val: f64) void;

pub fn setNumericAttrib(id: i32, name: []const u8, val: f64) void {
_setNumericAttrib(id, name.ptr, val);
pub fn setNumericAttrib(elementID: i32, name: []const u8, val: f64) void {
_setNumericAttrib(elementID, name.ptr, val);
}

pub extern "dom" fn _getNumericAttrib(id: i32, name: [*]const u8) f64;
pub extern "dom" fn _getNumericAttrib(elementID: i32, name: [*]const u8) f64;

pub fn getNumericAttrib(id: i32, name: []const u8) f64 {
return _getNumericAttrib(id, name.ptr);
pub fn getNumericAttrib(elementID: i32, name: []const u8) f64 {
return _getNumericAttrib(elementID, name.ptr);
}

pub extern "dom" fn _setInnerHtml(id: i32, ptr: [*]const u8) void;
pub extern "dom" fn _setInnerHtml(elementID: i32, ptr: [*]const u8) void;

pub fn setInnerHtml(id: i32, tag: []const u8) void {
_setInnerHtml(id, tag.ptr);
pub fn setInnerHtml(elementID: i32, tag: []const u8) void {
_setInnerHtml(elementID, tag.ptr);
}

pub extern "dom" fn _setInnerText(id: i32, ptr: [*]const u8) void;
pub extern "dom" fn _setInnerText(elementID: i32, ptr: [*]const u8) void;

pub fn setInnerText(id: i32, tag: []const u8) void {
_setInnerText(id, tag.ptr);
pub fn setInnerText(elementID: i32, tag: []const u8) void {
_setInnerText(elementID, tag.ptr);
}

export fn dom_callListener(listenerID: u32, event: *const dom.Event) void {
if (listeners.items[@as(u32, listenerID)]) |listener| listener(event);
export fn dom_callListener(listenerID: usize, event: *const dom.Event) void {
if (eventListeners.items[listenerID]) |listener| listener(event);
}

pub extern "dom" fn _addListener(id: i32, name: [*]const u8, listenerID: i32) void;
pub extern "dom" fn _addListener(elementID: i32, name: [*]const u8, listenerID: usize) void;

pub fn addListener(id: i32, name: []const u8, listener: EventListener) i32 {
nextListenerID += 1;
listeners.append(listener) catch return -1;
_addListener(id, name.ptr, nextListenerID);
return nextListenerID;
pub fn addListener(elementID: i32, name: []const u8, listener: EventListener) anyerror!usize {
const listenerID = try reuseOrAddSlot(EventListener, &eventListeners, listener);
_addListener(elementID, name.ptr, listenerID);
return listenerID;
}

pub extern "dom" fn _removeListener(listenerID: i32) void;
pub extern "dom" fn _removeListener(listenerID: usize) void;

pub fn removeListener(listenerID: i32) void {
if (listeners.items[listenerID]) {
pub fn removeListener(listenerID: usize) void {
if (eventListeners.items[listenerID]) {
_removeListener(listenerID);
listeners.items[listenerID] = null;
eventListeners.items[listenerID] = null;
}
}

Expand All @@ -97,3 +99,31 @@ pub extern "dom" fn preventDefault() void;
/// calls .stopImmediatePropagation() on currently processed event
/// (only to be called from an EventListener!)
pub extern "dom" fn stopImmediatePropagation() void;

pub extern "dom" fn _requestAnimationFrame(listenerID: usize) void;

pub fn requestAnimationFrame(listener: RAFListener) anyerror!usize {
const id = try reuseOrAddSlot(RAFListener, &rafListeners, listener);
_requestAnimationFrame(id);
return id;
}

export fn dom_callRAF(listenerID: usize, time: f64) void {
if (rafListeners.items[listenerID]) |raf| {
rafListeners.items[listenerID] = null;
raf(time);
}
}

/// Finds first available null slot in given arraylist and writes `item` there,
/// or appends item if no free slots are available
fn reuseOrAddSlot(comptime T: type, list: *std.ArrayList(?T), item: T) anyerror!usize {
if (std.mem.indexOfScalar(?T, list.items, null)) |id| {
list.items[id] = item;
return id;
} else {
const id = list.items.len;
try list.append(item);
return id;
}
}
3 changes: 3 additions & 0 deletions packages/wasm-api-dom/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./generated/api.js";

export interface DOMExports extends WasmExports {
dom_callListener(listenerID: number, event: number): void;
dom_callRAF(rafID: number, t: number): void;
}

export interface DOMImports extends WebAssembly.ModuleImports {
Expand Down Expand Up @@ -146,4 +147,6 @@ export interface DOMImports extends WebAssembly.ModuleImports {

_setInnerHtml(elementID: number, body: number): void;
_setInnerText(elementID: number, body: number): void;

_requestAnimationFrame(rafID: number): void;
}
31 changes: 30 additions & 1 deletion packages/wasm-api-dom/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ export class WasmDom implements IWasmAPI<DOMExports> {
? -1
: this.elements.find((x) => x === e.target, false);
event.target = target !== undefined ? target : -2;
let valueAddr = -1;
let valueLen: number;
if (e instanceof MouseEvent) {
const bounds = (<Element>(
e.target
Expand All @@ -166,9 +168,25 @@ export class WasmDom implements IWasmAPI<DOMExports> {
this.parent.setString(
e.key,
event.key.byteOffset,
8,
16,
true
);
} else if (e.type === "change" || e.type === "input") {
event.type = EventType.INPUT;
const el = <HTMLInputElement>e.target;
const value =
el.type === "checkbox"
? el.checked
? "on"
: "off"
: el.value;
const valueBytes =
this.parent.utf8Encoder.encode(value);
valueLen = valueBytes.length;
valueAddr = this.parent.allocate(valueLen + 1);
this.parent.u8.set(valueBytes, valueAddr);
this.parent.u8[valueAddr + valueLen] = 0;
event.value.setSlice(valueAddr, valueLen);
} else {
event.type = EventType.UNKOWN;
}
Expand All @@ -187,6 +205,10 @@ export class WasmDom implements IWasmAPI<DOMExports> {
listenerID,
event.__base
);
if (valueAddr >= 0) {
this.parent.free(valueAddr, valueLen! + 1);
valueAddr = -1;
}
this.currEvent = null;
};
this.parent.logger.debug(
Expand Down Expand Up @@ -231,6 +253,13 @@ export class WasmDom implements IWasmAPI<DOMExports> {
(<HTMLElement>this.elements.get(elementID)).innerText =
this.parent.getString(body);
},

_requestAnimationFrame: (rafID: number) => {
this.parent.logger.fine(`requestAnimationFrame #${rafID}`);
requestAnimationFrame((t) =>
this.parent.exports.dom_callRAF(rafID, t)
);
},
};
}

Expand Down
18 changes: 14 additions & 4 deletions packages/wasm-api-dom/src/generated/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Generated by @thi.ng/wasm-api at 2022-10-03T15:17:25.197Z - DO NOT EDIT!
* Generated by @thi.ng/wasm-api at 2022-10-04T09:31:43.431Z - DO NOT EDIT!
*/

// @ts-ignore possibly includes unused imports
Expand Down Expand Up @@ -112,22 +112,29 @@ export interface Event extends WasmTypeBase {
* Value/name of the key pressed
*/
key: Uint8Array;
/**
* INPUT event only, the value of the targeted input element.
* The memory is owned by the DOM API and will be freed immediatedly after the
* event handler has returned.
*/
value: WasmStringSlice;
}

export const $Event: WasmTypeConstructor<Event> = (mem) => ({
get align() {
return 4;
},
get size() {
return 28;
return 44;
},
instance: (base) => {
let $value: WasmStringSlice | null = null;
return {
get __base() {
return base;
},
get __bytes() {
return mem.u8.subarray(base, base + 28);
return mem.u8.subarray(base, base + 44);
},
get type(): EventType {
return mem.i32[base >>> 2];
Expand Down Expand Up @@ -179,7 +186,10 @@ export const $Event: WasmTypeConstructor<Event> = (mem) => ({
},
get key(): Uint8Array {
const addr = (base + 18);
return mem.u8.subarray(addr, addr + 8);
return mem.u8.subarray(addr, addr + 15);
},
get value(): WasmStringSlice {
return $value || ($value = new WasmStringSlice(mem, (base + 36), true));
},
};
}
Expand Down
7 changes: 6 additions & 1 deletion packages/wasm-api-dom/typedefs.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,14 @@
"name": "key",
"type": "u8",
"tag": "array",
"len": 8,
"len": 15,
"sentinel": 0,
"doc": "Value/name of the key pressed"
},
{
"name": "value",
"type": "string",
"doc": "INPUT event only, the value of the targeted input element.\nThe memory is owned by the DOM API and will be freed immediatedly after the event handler has returned."
}
],
"body": {
Expand Down

0 comments on commit e563ccc

Please sign in to comment.