wasmserve: almost working

This commit is contained in:
Ali Chraghi 2023-03-26 19:48:37 +03:30 committed by Stephen Gutekanst
parent 64b19d559e
commit b086bdee3a
5 changed files with 122 additions and 163 deletions

View file

@ -10,8 +10,7 @@ const earcut = @import("libs/earcut/build.zig");
const gamemode = @import("libs/gamemode/build.zig"); const gamemode = @import("libs/gamemode/build.zig");
const model3d = @import("libs/model3d/build.zig"); const model3d = @import("libs/model3d/build.zig");
const dusk = @import("libs/dusk/build.zig"); const dusk = @import("libs/dusk/build.zig");
// TODO: get wasmserve working const wasmserve = @import("tools/wasmserve/wasmserve.zig");
// const wasmserve = @import("tools/wasmserve/wasmserve.zig");
pub const gpu_dawn = @import("libs/gpu-dawn/sdk.zig").Sdk(.{ pub const gpu_dawn = @import("libs/gpu-dawn/sdk.zig").Sdk(.{
.glfw_include_dir = sdkPath("/libs/glfw/upstream/glfw/include"), .glfw_include_dir = sdkPath("/libs/glfw/upstream/glfw/include"),
.system_sdk = system_sdk, .system_sdk = system_sdk,
@ -28,8 +27,7 @@ const core = @import("libs/core/sdk.zig").Sdk(.{
.gpu_dawn = gpu_dawn, .gpu_dawn = gpu_dawn,
.glfw = glfw, .glfw = glfw,
.gamemode = gamemode, .gamemode = gamemode,
// TODO: get wasmserve working .wasmserve = wasmserve,
// .wasmserve = wasmserve,
.sysjs = sysjs, .sysjs = sysjs,
}); });

View file

@ -91,7 +91,7 @@ pub fn Sdk(comptime deps: anytype) type {
pub const LinkError = deps.glfw.LinkError; pub const LinkError = deps.glfw.LinkError;
pub const RunError = error{ pub const RunError = error{
ParsingIpFailed, ParsingIpFailed,
} || std.fmt.ParseIntError; } || deps.wasmserve.Error || std.fmt.ParseIntError;
pub const Platform = enum { pub const Platform = enum {
native, native,
@ -129,7 +129,12 @@ pub fn Sdk(comptime deps: anytype) type {
const step = blk: { const step = blk: {
if (platform == .web) { if (platform == .web) {
const lib = b.addSharedLibrary(.{ .name = options.name, .root_source_file = .{ .path = sdkPath("/src/main.zig") }, .target = options.target, .optimize = options.optimize }); const lib = b.addSharedLibrary(.{
.name = options.name,
.root_source_file = .{ .path = sdkPath("/src/entry.zig") },
.target = options.target,
.optimize = options.optimize,
});
lib.rdynamic = true; lib.rdynamic = true;
lib.addModule("sysjs", deps.sysjs.module(b)); lib.addModule("sysjs", deps.sysjs.module(b));
@ -182,7 +187,7 @@ pub fn Sdk(comptime deps: anytype) type {
// Set install directory to '{prefix}/www' // Set install directory to '{prefix}/www'
app.getInstallStep().?.dest_dir = web_install_dir; app.getInstallStep().?.dest_dir = web_install_dir;
inline for (.{ "/src/platform/wasm/mach.js", "/libs/sysjs/src/mach-sysjs.js" }) |js| { inline for (.{ "/src/platform/wasm/mach.js", "/libs/mach-sysjs/src/mach-sysjs.js" }) |js| {
const install_js = app.b.addInstallFileWithDir( const install_js = app.b.addInstallFileWithDir(
.{ .path = sdkPath(js) }, .{ .path = sdkPath(js) },
web_install_dir, web_install_dir,
@ -191,15 +196,9 @@ pub fn Sdk(comptime deps: anytype) type {
app.getInstallStep().?.step.dependOn(&install_js.step); app.getInstallStep().?.step.dependOn(&install_js.step);
} }
const html_generator = app.b.addExecutable(.{ genHtml(app.b.allocator, app.b.getInstallPath(web_install_dir, "index.html"), app.name) catch |err| {
.name = "html-generator", std.log.err("unable to generate html: {s}", .{@errorName(err)});
.root_source_file = .{ .path = sdkPath("/tools/html-generator/main.zig") }, };
});
const run_html_generator = html_generator.run();
run_html_generator.addArgs(&.{ "index.html", app.name });
run_html_generator.cwd = app.b.getInstallPath(web_install_dir, "");
app.getInstallStep().?.step.dependOn(&run_html_generator.step);
} }
// Install resources // Install resources
@ -218,20 +217,19 @@ pub fn Sdk(comptime deps: anytype) type {
pub fn run(app: *const App) RunError!*std.build.Step { pub fn run(app: *const App) RunError!*std.build.Step {
if (app.platform == .web) { if (app.platform == .web) {
@panic("TODO: wasmserve is broken! sorry"); const address = std.process.getEnvVarOwned(app.b.allocator, "MACH_ADDRESS") catch try app.b.allocator.dupe(u8, "127.0.0.1");
// TODO: get wasmserve working const port = std.process.getEnvVarOwned(app.b.allocator, "MACH_PORT") catch try app.b.allocator.dupe(u8, "8080");
// const address = std.process.getEnvVarOwned(app.b.allocator, "MACH_ADDRESS") catch try app.b.allocator.dupe(u8, "127.0.0.1"); const address_parsed = std.net.Address.parseIp4(address, try std.fmt.parseInt(u16, port, 10)) catch return error.ParsingIpFailed;
// const port = std.process.getEnvVarOwned(app.b.allocator, "MACH_PORT") catch try app.b.allocator.dupe(u8, "8080"); const serve_step = try deps.wasmserve.serve(
// const address_parsed = std.net.Address.parseIp4(address, try std.fmt.parseInt(u16, port, 10)) catch return error.ParsingIpFailed; app.b,
// const serve_step = try deps.wasmserve.serve( .{
// app.step, // .step_name =
// .{ .install_dir = web_install_dir,
// .install_dir = web_install_dir, .watch_paths = app.watch_paths,
// .watch_paths = app.watch_paths, .listen_address = address_parsed,
// .listen_address = address_parsed, },
// }, );
// ); return &serve_step.step;
// return &serve_step.step;
} else { } else {
return &app.step.run().step; return &app.step.run().step;
} }
@ -241,5 +239,72 @@ pub fn Sdk(comptime deps: anytype) type {
return app.step.install_step; return app.step.install_step;
} }
}; };
pub fn genHtml(allocator: std.mem.Allocator, output_name: []const u8, app_name: []const u8) !void {
const file = try std.fs.cwd().createFile(output_name, .{});
defer file.close();
var buf = try std.fmt.allocPrint(allocator, html_template, .{ .app_name = app_name });
defer allocator.free(buf);
_ = try file.write(buf);
}
const html_template =
\\<!doctype html>
\\<html>
\\
\\<head>
\\ <meta charset="utf-8">
\\ <title>{[app_name]s}</title>
\\</head>
\\
\\<body>
\\ <script type="module">
\\ import {{ mach }} from "./mach.js";
\\ import {{ sysjs }} from "./mach-sysjs.js";
\\ import setupWasmserve from "./wasmserve.js";
\\
\\ setupWasmserve();
\\
\\ let imports = {{
\\ mach,
\\ sysjs,
\\ }};
\\
\\ fetch("{[app_name]s}.wasm")
\\ .then(response => response.arrayBuffer())
\\ .then(buffer => WebAssembly.instantiate(buffer, imports))
\\ .then(results => results.instance)
\\ .then(instance => {{
\\ sysjs.init(instance);
\\ mach.init(instance);
\\ instance.exports.wasmInit();
\\
\\ let frame = true;
\\ let last_update_time = performance.now();
\\ let update = function () {{
\\ if (!frame) {{
\\ instance.exports.wasmDeinit();
\\ return;
\\ }}
\\ if (mach.machHasEvent() ||
\\ last_update_time + mach.wait_timeout * 1000 <= performance.now()) {{
\\ if (instance.exports.wasmUpdate()) {{
\\ instance.exports.wasmDeinit();
\\ return;
\\ }}
\\ last_update_time = performance.now();
\\ }}
\\ window.requestAnimationFrame(update);
\\ }};
\\ window.requestAnimationFrame(update);
\\ }})
\\ .catch(err => console.error(err));
\\ </script>
\\</body>
\\
\\</html>
;
}; };
} }

View file

@ -1,29 +0,0 @@
const std = @import("std");
const source = @embedFile("template.html");
const app_name_needle = "{ app_name }";
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
if (args.len < 3) {
std.debug.print("Usage: html-generator <output-name> <app-name>\n", .{});
return;
}
const output_name = args[1];
const app_name = args[2];
const file = try std.fs.cwd().createFile(output_name, .{});
defer file.close();
var buf = try allocator.alloc(u8, std.mem.replacementSize(u8, source, app_name_needle, app_name));
defer allocator.free(buf);
_ = std.mem.replace(u8, source, app_name_needle, app_name, buf);
_ = try file.write(buf);
}

View file

@ -1,54 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{ app_name }</title>
</head>
<body>
<script type="module">
import { mach } from "./mach.js";
import { sysjs } from "./mach-sysjs.js";
import setupWasmserve from "./wasmserve.js";
setupWasmserve();
let imports = {
mach,
sysjs,
};
fetch("{ app_name }.wasm")
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.instantiate(buffer, imports))
.then(results => results.instance)
.then(instance => {
sysjs.init(instance);
mach.init(instance);
instance.exports.wasmInit();
let frame = true;
let last_update_time = performance.now();
let update = function () {
if (!frame) {
instance.exports.wasmDeinit();
return;
}
if (mach.machHasEvent() ||
last_update_time + mach.wait_timeout * 1000 <= performance.now()) {
if (instance.exports.wasmUpdate()) {
instance.exports.wasmDeinit();
return;
}
last_update_time = performance.now();
}
window.requestAnimationFrame(update);
};
window.requestAnimationFrame(update);
})
.catch(err => console.error(err));
</script>
</body>
</html>

View file

@ -19,6 +19,7 @@ const esc = struct {
}; };
pub const Options = struct { pub const Options = struct {
step_name: []const u8 = "install",
install_dir: ?build.InstallDir = null, install_dir: ?build.InstallDir = null,
watch_paths: ?[]const []const u8 = null, watch_paths: ?[]const []const u8 = null,
listen_address: ?net.Address = null, listen_address: ?net.Address = null,
@ -26,30 +27,30 @@ pub const Options = struct {
pub const Error = error{CannotOpenDirectory} || mem.Allocator.Error; pub const Error = error{CannotOpenDirectory} || mem.Allocator.Error;
pub fn serve(step: *build.CompileStep, options: Options) Error!*Wasmserve { pub fn serve(b: *std.Build.Builder, options: Options) Error!*Wasmserve {
const self = try step.step.owner.allocator.create(Wasmserve); const self = try b.allocator.create(Wasmserve);
const install_dir = options.install_dir orelse build.InstallDir{ .lib = {} }; const install_dir = options.install_dir orelse build.InstallDir{ .lib = {} };
const install_dir_iter = fs.cwd().makeOpenPathIterable(step.step.owner.getInstallPath(install_dir, ""), .{}) catch const install_dir_iter = fs.cwd().makeOpenPathIterable(b.getInstallPath(install_dir, ""), .{}) catch
return error.CannotOpenDirectory; return error.CannotOpenDirectory;
self.* = Wasmserve{ self.* = Wasmserve{
.step = build.Step.init(.run, "wasmserve", step.step.owner.allocator, Wasmserve.make), .b = b,
.b = step.step.owner, .step = build.Step.init(.{ .id = .run, .name = "wasmserve", .owner = b, .makeFn = Wasmserve.make }),
.exe_step = step, .step_name = options.step_name,
.install_dir = install_dir, .install_dir = install_dir,
.install_dir_iter = install_dir_iter, .install_dir_iter = install_dir_iter,
.address = options.listen_address orelse net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 8080), .address = options.listen_address orelse net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 8080),
.subscriber = null, .subscriber = null,
.watch_paths = options.watch_paths orelse &.{step.root_src.?.path}, .watch_paths = options.watch_paths orelse &.{"src"},
.mtimes = std.AutoHashMap(fs.File.INode, i128).init(step.step.owner.allocator), .mtimes = std.AutoHashMap(fs.File.INode, i128).init(b.allocator),
.notify_msg = null, .notify_msg = null,
}; };
return self; return self;
} }
const Wasmserve = struct { const Wasmserve = struct {
step: build.Step,
b: *build.Builder, b: *build.Builder,
exe_step: *build.CompileStep, step: build.Step,
step_name: []const u8,
install_dir: build.InstallDir, install_dir: build.InstallDir,
install_dir_iter: fs.IterableDir, install_dir_iter: fs.IterableDir,
address: net.Address, address: net.Address,
@ -69,11 +70,13 @@ const Wasmserve = struct {
data: []const u8, data: []const u8,
}; };
pub fn make(step: *build.Step) !void { pub fn make(step: *build.Step, prog_node: *std.Progress.Node) !void {
std.debug.print("Really!\n", .{});
const self = @fieldParentPtr(Wasmserve, "step", step); const self = @fieldParentPtr(Wasmserve, "step", step);
self.compile(); try self.compile();
std.debug.assert(mem.eql(u8, fs.path.extension(self.exe_step.out_filename), ".wasm")); // std.debug.assert(mem.eql(u8, fs.path.extension(self.compile_step.out_filename), ".wasm"));
var www_dir = try fs.cwd().openIterableDir(www_dir_path, .{}); var www_dir = try fs.cwd().openIterableDir(www_dir_path, .{});
defer www_dir.close(); defer www_dir.close();
@ -86,7 +89,7 @@ const Wasmserve = struct {
self.install_dir, self.install_dir,
file.name, file.name,
); );
try install_www.step.make(); try install_www.step.make(prog_node);
} }
const watch_thread = try std.Thread.spawn(.{}, watch, .{self}); const watch_thread = try std.Thread.spawn(.{}, watch, .{self});
@ -134,7 +137,12 @@ const Wasmserve = struct {
const url = dropFragment(uri)[1..]; const url = dropFragment(uri)[1..];
if (mem.eql(u8, url, "notify")) { if (mem.eql(u8, url, "notify")) {
_ = try conn.stream.write("HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\nContent-Type: text/event-stream\r\nCache-Control: No-Cache\r\n\r\n"); _ = try conn.stream.write(
"HTTP/1.1 200 OK\r\n" ++
"Connection: Keep-Alive\r\n" ++
"Content-Type: text/event-stream\r\n" ++
"Cache-Control: No-Cache\r\n\r\n",
);
self.subscriber = try self.b.allocator.create(net.StreamServer.Connection); self.subscriber = try self.b.allocator.create(net.StreamServer.Connection);
self.subscriber.?.* = conn; self.subscriber.?.* = conn;
if (self.notify_msg) |msg| if (self.notify_msg) |msg|
@ -232,7 +240,7 @@ const Wasmserve = struct {
const entry = try self.mtimes.getOrPut(stat.inode); const entry = try self.mtimes.getOrPut(stat.inode);
if (entry.found_existing and stat.mtime > entry.value_ptr.*) { if (entry.found_existing and stat.mtime > entry.value_ptr.*) {
std.log.info(esc.yellow ++ esc.underline ++ "{s}" ++ esc.reset ++ " updated", .{path}); std.log.info(esc.yellow ++ esc.underline ++ "{s}" ++ esc.reset ++ " updated", .{path});
self.compile(); try self.compile();
entry.value_ptr.* = stat.mtime; entry.value_ptr.* = stat.mtime;
return true; return true;
} }
@ -253,14 +261,10 @@ const Wasmserve = struct {
} }
} }
fn compile(self: *Wasmserve) void { fn compile(self: *Wasmserve) !void {
std.log.info("Building...", .{}); std.log.info("Building...", .{});
const argv = getExecArgs(self.exe_step) catch |err| {
logErr(err, @src()); var res = std.ChildProcess.exec(.{ .argv = &.{ self.b.zig_exe, "build", self.step_name, "-Dtarget=wasm32-freestanding-none" }, .allocator = self.b.allocator }) catch |err| {
return;
};
defer self.b.allocator.free(argv);
var res = std.ChildProcess.exec(.{ .argv = argv, .allocator = self.b.allocator }) catch |err| {
logErr(err, @src()); logErr(err, @src());
return; return;
}; };
@ -307,8 +311,8 @@ fn dropFragment(input: []const u8) []const u8 {
} }
fn logErr(err: anyerror, src: std.builtin.SourceLocation) void { fn logErr(err: anyerror, src: std.builtin.SourceLocation) void {
if (@errorReturnTrace()) |bt| { if (@errorReturnTrace()) |et| {
std.log.err(esc.red ++ esc.bold ++ "{s}" ++ esc.reset ++ " >>>\n{s}", .{ @errorName(err), bt }); std.log.err(esc.red ++ esc.bold ++ "{s}" ++ esc.reset ++ " >>>\n{s}", .{ @errorName(err), et });
} else { } else {
var file_name_buf: [1024]u8 = undefined; var file_name_buf: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&file_name_buf); var fba = std.heap.FixedBufferAllocator.init(&file_name_buf);
@ -333,28 +337,3 @@ fn sdkPath(comptime suffix: []const u8) []const u8 {
break :blk root_dir ++ suffix; break :blk root_dir ++ suffix;
}; };
} }
// copied from CompileStep.make()
// TODO: this is very tricky
// TODO(wasmserve): wasmserve is broken after recent Zig build changes, need to expose
// this from Zig stdlib or something instead of copying this huge function out of stdlib
// like this (nasty!)
fn getExecArgs(_: *build.CompileStep) ![]const []const u8 {
@panic("wasmserve is currently not working");
}
fn makePackageCmd(self: *std.build.CompileStep, pkg: std.build.Pkg, zig_args: *std.ArrayList([]const u8)) error{OutOfMemory}!void {
const builder = self.builder;
try zig_args.append("--pkg-begin");
try zig_args.append(pkg.name);
try zig_args.append(builder.pathFromRoot(pkg.source.getPath(self.builder)));
if (pkg.dependencies) |dependencies| {
for (dependencies) |sub_pkg| {
try makePackageCmd(self, sub_pkg, zig_args);
}
}
try zig_args.append("--pkg-end");
}