Skip to content

Commit 6448ebf

Browse files
committed
Add Listener and bring back WASM in some shape or form
While unsatisfactory and causing performance issues because Zig async is missing, WebAssembly is nonetheless being brought back at the expense of having to change the entire event loop architecture of Capy. This is at best a temporary solution
1 parent 702936d commit 6448ebf

File tree

8 files changed

+234
-106
lines changed

8 files changed

+234
-106
lines changed

build_capy.zig

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,32 @@ const WebServerStep = struct {
9292
content_type = "application/wasm";
9393
}
9494

95-
var file = try std.fs.openFileAbsolute(file_path, .{ .mode = .read_only });
96-
defer file.close();
97-
const content = try file.readToEndAlloc(req_allocator, std.math.maxInt(usize));
95+
std.log.info("{s}", .{path});
96+
const file: ?std.fs.File = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch |err| blk: {
97+
switch (err) {
98+
error.FileNotFound => break :blk null,
99+
else => return err,
100+
}
101+
};
102+
const content = blk: {
103+
if (file) |f| {
104+
defer f.close();
105+
break :blk try f.readToEndAlloc(req_allocator, std.math.maxInt(usize));
106+
} else {
107+
break :blk "404 Not Found";
108+
}
109+
};
98110

99111
res.transfer_encoding = .{ .content_length = content.len };
100-
try res.headers.append("Connection", res.request.headers.getFirstValue("Connection") orelse "close");
112+
// try res.headers.append("Connection", res.request.headers.getFirstValue("Connection") orelse "close");
113+
try res.headers.append("Connection", "close");
101114
try res.headers.append("Content-Type", content_type);
102115

103116
try res.do();
104117
try res.writer().writeAll(content);
105118
try res.finish();
106119

107-
if (res.connection.closing) break;
120+
if (res.connection.closing or true) break;
108121
}
109122
}
110123
};
@@ -277,6 +290,7 @@ pub fn install(step: *std.Build.CompileStep, options: CapyBuildOptions) !*std.Bu
277290
// Things like the image reader require more stack than given by default
278291
// TODO: remove once ziglang/zig#12589 is merged
279292
step.stack_size = @max(step.stack_size orelse 0, 256 * 1024);
293+
step.export_symbol_names = &.{ "_start", "_capyStep" };
280294
if (step.optimize == .ReleaseSmall) {
281295
step.strip = true;
282296
}

examples/colors.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub fn animateRandomColor(button_: *anyopaque) !void {
1515
rect.color.animate(capy.Easings.InOut, randomColor, 1000);
1616
}
1717

18-
pub fn main() !void {
18+
pub fn start() !void {
1919
try capy.init();
2020
var window = try capy.Window.init();
2121
prng = std.rand.DefaultPrng.init(@as(u64, @bitCast(std.time.milliTimestamp())));
@@ -28,5 +28,5 @@ pub fn main() !void {
2828
}),
2929
}));
3030
window.show();
31-
capy.runEventLoop();
31+
// capy.runEventLoop();
3232
}

src/backends/macos/backend.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ pub const Window = struct {
160160

161161
pub fn show(self: *Window) void {
162162
std.log.info("show window", .{});
163-
objc.msgSendByName(void, self.peer, "setIsVisible:", .{ @as(objc.id, self.peer), @as(u8, @intFromBool(true))}) catch unreachable;
163+
objc.msgSendByName(void, self.peer, "setIsVisible:", .{ @as(objc.id, self.peer), @as(u8, @intFromBool(true)) }) catch unreachable;
164164
objc.msgSendByName(void, self.peer, "makeKeyAndOrderFront:", .{@as(objc.id, self.peer)}) catch unreachable;
165165
std.log.info("showed window", .{});
166166
_ = activeWindows.fetchAdd(1, .Release);

src/backends/wasm/backend.zig

Lines changed: 76 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ const GuiWidget = struct {
2222
element: js.ElementId = 0,
2323

2424
processEventFn: *const fn (object: ?*anyopaque, event: js.EventId) void,
25+
children: std.ArrayList(*GuiWidget),
2526

2627
pub fn init(comptime T: type, allocator: std.mem.Allocator, name: []const u8, typeName: []const u8) !*GuiWidget {
2728
const self = try allocator.create(GuiWidget);
28-
self.* = .{ .processEventFn = T.processEvent, .element = js.createElement(name, typeName) };
29+
self.* = .{
30+
.processEventFn = T.processEvent,
31+
.element = js.createElement(name, typeName),
32+
.children = std.ArrayList(*GuiWidget).init(allocator),
33+
};
2934
return self;
3035
}
3136
};
@@ -46,11 +51,16 @@ pub fn init() !void {
4651
var globalWindow: ?*Window = null;
4752

4853
pub const Window = struct {
54+
peer: *GuiWidget,
4955
child: ?PeerType = null,
5056
scale: f32 = 1.0,
5157

58+
pub usingnamespace Events(Window);
59+
5260
pub fn create() !Window {
53-
return Window{};
61+
return Window{
62+
.peer = try GuiWidget.init(Window, lasting_allocator, "div", "window"),
63+
};
5464
}
5565

5666
pub fn show(self: *Window) void {
@@ -106,6 +116,7 @@ pub fn Events(comptime T: type) type {
106116
}
107117

108118
pub inline fn setCallback(self: *T, comptime eType: EventType, cb: anytype) !void {
119+
self.peer.object = self;
109120
switch (eType) {
110121
.Click => self.peer.user.clickHandler = cb,
111122
.Draw => self.peer.user.drawHandler = cb,
@@ -119,6 +130,7 @@ pub fn Events(comptime T: type) type {
119130
},
120131
.KeyType => self.peer.user.keyTypeHandler = cb,
121132
.KeyPress => self.peer.user.keyPressHandler = cb,
133+
.PropertyChange => self.peer.user.propertyChangeHandler = cb,
122134
}
123135
}
124136

@@ -181,18 +193,18 @@ pub fn Events(comptime T: type) type {
181193
},
182194
}
183195
} else if (T == Container) { // if we're a container, iterate over our children to propagate the event
184-
for (self.children.items) |child| {
196+
for (self.peer.children.items) |child| {
185197
child.processEventFn(child.object, event);
186198
}
187199
}
188200
}
189201

190202
pub fn getWidth(self: *const T) c_int {
191-
return std.math.max(10, js.getWidth(self.peer.element));
203+
return @max(10, js.getWidth(self.peer.element));
192204
}
193205

194206
pub fn getHeight(self: *const T) c_int {
195-
return std.math.max(10, js.getHeight(self.peer.element));
207+
return @max(10, js.getHeight(self.peer.element));
196208
}
197209

198210
pub fn getPreferredSize(self: *const T) lib.Size {
@@ -241,7 +253,7 @@ pub const TextField = struct {
241253
pub const Label = struct {
242254
peer: *GuiWidget,
243255
/// The text returned by getText(), it's invalidated everytime setText is called
244-
temp_text: ?[:0]const u8 = null,
256+
temp_text: ?[]const u8 = null,
245257

246258
pub usingnamespace Events(Label);
247259

@@ -375,6 +387,11 @@ pub const Canvas = struct {
375387
js.rectPath(self.ctx, x, y, w, h);
376388
}
377389

390+
pub fn roundedRectangleEx(self: *DrawContext, x: i32, y: i32, w: u32, h: u32, corner_radiuses: [4]f32) void {
391+
_ = corner_radiuses;
392+
js.rectPath(self.ctx, x, y, w, h);
393+
}
394+
378395
pub fn text(self: *DrawContext, x: i32, y: i32, layout: TextLayout, str: []const u8) void {
379396
// TODO: layout
380397
_ = layout;
@@ -447,20 +464,28 @@ pub const ImageData = struct {
447464

448465
pub const Container = struct {
449466
peer: *GuiWidget,
450-
children: std.ArrayList(*GuiWidget),
451467

452468
pub usingnamespace Events(Container);
453469

454470
pub fn create() !Container {
455471
return Container{
456472
.peer = try GuiWidget.init(Container, lasting_allocator, "div", "container"),
457-
.children = std.ArrayList(*GuiWidget).init(lasting_allocator),
458473
};
459474
}
460475

461476
pub fn add(self: *Container, peer: PeerType) void {
462477
js.appendElement(self.peer.element, peer.element);
463-
self.children.append(peer) catch unreachable;
478+
self.peer.children.append(peer) catch unreachable;
479+
}
480+
481+
pub fn remove(self: *const Container, peer: PeerType) void {
482+
_ = peer;
483+
_ = self;
484+
}
485+
486+
pub fn setTabOrder(self: *Container, peers: []const PeerType) void {
487+
_ = peers;
488+
_ = self;
464489
}
465490

466491
pub fn move(self: *const Container, peer: PeerType, x: u32, y: u32) void {
@@ -498,22 +523,28 @@ pub const HttpResponse = struct {
498523

499524
// Execution
500525

501-
fn executeMain() callconv(.Async) void {
502-
const mainFn = @import("root").main;
503-
const ReturnType = @typeInfo(@TypeOf(mainFn)).Fn.return_type.?;
526+
fn executeStart() void {
527+
const startFn = @import("root").start;
528+
const ReturnType = @typeInfo(@TypeOf(startFn)).Fn.return_type.?;
504529
if (ReturnType == void) {
505-
mainFn();
530+
startFn();
506531
} else {
507-
mainFn() catch |err| @panic(@errorName(err));
532+
startFn() catch |err| @panic(@errorName(err));
508533
}
509-
js.stopExecution();
510534
}
511535

512-
var frame: @Frame(executeMain) = undefined;
513-
var result: void = {};
514-
var suspending: bool = false;
515-
516-
var resumePtr: anyframe = undefined;
536+
fn executeStep() void {
537+
// Check for events
538+
while (js.hasEvent()) {
539+
const eventId = js.popEvent();
540+
if (globalWindow) |window| {
541+
if (window.child) |child| {
542+
child.processEventFn(child.object, eventId);
543+
}
544+
}
545+
}
546+
lib.eventStep.callListeners();
547+
}
517548

518549
fn milliTimestamp() i64 {
519550
return @as(i64, @intFromFloat(js.now()));
@@ -556,10 +587,11 @@ pub const backendExport = struct {
556587

557588
const start = milliTimestamp();
558589
while (milliTimestamp() < start + @as(i64, @intCast(duration))) {
559-
suspending = true;
560-
suspend {
561-
resumePtr = @frame();
562-
}
590+
// TODO: when zig async is restored, use suspend here
591+
// suspending = true;
592+
// suspend {
593+
// resumePtr = @frame();
594+
// }
563595
}
564596
return 0;
565597
}
@@ -596,41 +628,35 @@ pub const backendExport = struct {
596628
}
597629

598630
pub fn panic(msg: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
631+
@setRuntimeSafety(false);
599632
js.print(msg);
600633

601634
//@breakpoint();
602635
js.stopExecution();
603636
}
604637

605638
pub export fn _start() callconv(.C) void {
606-
_ = @asyncCall(&frame, &result, executeMain, .{});
639+
executeStart();
607640
}
608641

609-
pub export fn _zgtContinue() callconv(.C) void {
610-
if (suspending) {
611-
suspending = false;
612-
resume resumePtr;
613-
}
642+
pub export fn _capyStep() callconv(.C) void {
643+
executeStep();
614644
}
615645
};
616646

617-
pub fn runStep(step: shared.EventLoopStep) callconv(.Async) bool {
618-
_ = step;
619-
while (js.hasEvent()) {
620-
const eventId = js.popEvent();
621-
switch (js.getEventType(eventId)) {
622-
else => {
623-
if (globalWindow) |window| {
624-
if (window.child) |child| {
625-
child.processEventFn(child.object, eventId);
626-
}
627-
}
628-
},
629-
}
630-
}
631-
suspending = true;
632-
suspend {
633-
resumePtr = @frame();
634-
}
635-
return true;
636-
}
647+
// pub fn runStep(step: shared.EventLoopStep) callconv(.Async) bool {
648+
// _ = step;
649+
// while (js.hasEvent()) {
650+
// const eventId = js.popEvent();
651+
// switch (js.getEventType(eventId)) {
652+
// else => {
653+
// if (globalWindow) |window| {
654+
// if (window.child) |child| {
655+
// child.processEventFn(child.object, eventId);
656+
// }
657+
// }
658+
// },
659+
// }
660+
// }
661+
// return true;
662+
// }

src/backends/wasm/capy.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ const importObj = {
3737
elem.style.position = "absolute";
3838
elem.classList.add("capy-" + readString(elementType, elementTypeLen));
3939
elem.addEventListener("click", function(e) {
40-
pushEvent({
41-
type: 1,
42-
target: idx
43-
});
40+
if (elem.nodeName == "BUTTON") {
41+
pushEvent({
42+
type: 1,
43+
target: idx
44+
});
45+
}
4446
});
4547
elem.addEventListener("change", function(e) {
4648
pushEvent({
@@ -340,7 +342,7 @@ const importObj = {
340342
// TODO: when we're in blocking mode, avoid updating so often
341343
function update() {
342344
if (executeProgram) {
343-
obj.instance.exports._zgtContinue();
345+
obj.instance.exports._capyStep();
344346
requestAnimationFrame(update);
345347
}
346348
}

src/data.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub var _animatedAtoms = std.ArrayList(struct {
103103
fnPtr: *const fn (data: *anyopaque) bool,
104104
userdata: *anyopaque,
105105
}).init(lasting_allocator);
106+
pub var _animatedAtomsLength = Atom(usize).of(0);
106107
pub var _animatedAtomsMutex = std.Thread.Mutex{};
107108

108109
fn isAnimableType(comptime T: type) bool {
@@ -296,6 +297,7 @@ pub fn Atom(comptime T: type) type {
296297
}
297298
if (!contains) {
298299
_animatedAtoms.append(.{ .fnPtr = @as(*const fn (*anyopaque) bool, @ptrCast(&Self.update)), .userdata = self }) catch {};
300+
_animatedAtomsLength.set(_animatedAtoms.items.len);
299301
}
300302
}
301303

@@ -847,6 +849,7 @@ test "animated atom" {
847849
var animated = try Atom(i32).animated(&original, Easings.Linear, 1000);
848850
defer animated.deinit();
849851
defer _animatedAtoms.clearAndFree();
852+
_animatedAtomsLength.set(0);
850853

851854
original.set(1000);
852855
try std.testing.expect(animated.hasAnimation());

0 commit comments

Comments
 (0)