diff --git a/build.zig b/build.zig index 0ef79a7d..678e7c2d 100644 --- a/build.zig +++ b/build.zig @@ -50,25 +50,6 @@ pub fn build(b: *std.Build) !void { if (target.getCpuArch() != .wasm32) { const tests_step = b.step("test", "Run tests"); tests_step.dependOn(&testStep(b, optimize, target).step); - - const editor = try App.init( - b, - .{ - .name = "mach", - .src = "src/editor/app.zig", - .custom_entrypoint = "src/editor/main.zig", - .target = target, - .optimize = optimize, - .mach_builder = b, - }, - ); - try editor.link(); - - const editor_install_step = b.step("editor", "Install editor"); - editor_install_step.dependOn(&editor.install.step); - - const editor_run_step = b.step("run", "Run the editor"); - editor_run_step.dependOn(&editor.run.step); } } diff --git a/src/editor/Builder.zig b/src/editor/Builder.zig deleted file mode 100644 index 884c44f8..00000000 --- a/src/editor/Builder.zig +++ /dev/null @@ -1,353 +0,0 @@ -const std = @import("std"); -const mime_map = @import("Builder/mime.zig").mime_map; -const Target = @import("target.zig").Target; -const OptimizeMode = std.builtin.OptimizeMode; -const allocator = @import("main.zig").allocator; - -const Builder = @This(); - -const out_dir_path = "zig-out/www"; -const @"www/index.html" = @embedFile("Builder/www/index.html"); -const @"www/ansi_to_html.js" = @embedFile("Builder/www/ansi_to_html.js"); -const @"www/wasmserve.js" = @embedFile("Builder/www/wasmserve.js"); -const @"www/favicon.ico" = @embedFile("Builder/www/favicon.ico"); - -steps: []const []const u8 = &.{}, -serve: bool = false, -target: ?Target = null, -optimize: OptimizeMode = .Debug, -zig_path: []const u8 = "zig", -zig_build_args: []const []const u8 = &.{}, - -status: Status = .building, -listen_port: u16 = 1717, -subscribers: std.ArrayListUnmanaged(std.net.Stream) = .{}, -watch_paths: ?[][]const u8 = null, -mtimes: std.AutoHashMapUnmanaged(std.fs.File.INode, i128) = .{}, -www_index_html: []const u8 = undefined, - -const Status = union(enum) { - building, - built, - stopped, - compile_error: []const u8, -}; - -fn deinit(self: *Builder) void { - allocator.free(self.steps); - allocator.free(self.zig_build_args); - if (self.watch_paths) |wp| allocator.free(wp); -} - -pub fn run(self: *Builder) !void { - defer self.deinit(); - - var child = try self.runZigBuild(.Inherit); - switch (try child.wait()) { - .Exited => |code| { - if (code != 0) std.os.exit(code); - }, - else => std.os.exit(1), - } - - if (self.serve) { - var out_dir = std.fs.cwd().openIterableDir(out_dir_path, .{}) catch |err| { - std.log.err("cannot open '{s}': {s}", .{ out_dir_path, @errorName(err) }); - std.os.exit(1); - }; - defer out_dir.close(); - - var wasm_file_name: ?[]const u8 = null; - var out_dir_iter = out_dir.iterate(); - while (try out_dir_iter.next()) |entry| { - if (entry.kind != .file) continue; - if (std.mem.eql(u8, std.fs.path.extension(entry.name), ".wasm")) { - wasm_file_name = try allocator.dupe(u8, entry.name); - } - } - if (wasm_file_name == null) { - std.log.err("no WASM binary found at '{s}'", .{out_dir_path}); - std.os.exit(1); - } - - self.www_index_html = try std.fmt.allocPrint( - allocator, - @"www/index.html", - .{ - .app_name = std.fs.path.stem(wasm_file_name.?), - .wasm_path = wasm_file_name.?, - }, - ); - - const watch_thread = if (self.watch_paths) |_| - try std.Thread.spawn(.{}, watch, .{self}) - else - null; - defer if (watch_thread) |wt| wt.detach(); - - var server = std.net.StreamServer.init(.{ .reuse_address = true }); - defer server.deinit(); - try server.listen(std.net.Address.initIp4(.{ 127, 0, 0, 1 }, self.listen_port)); - std.log.info("started listening at http://127.0.0.1:{d}...", .{self.listen_port}); - - var pool = try allocator.create(std.Thread.Pool); - try pool.init(.{ .allocator = allocator }); - defer pool.deinit(); - - while (true) { - const conn = try server.accept(); - try pool.spawn(handleConn, .{ self, conn }); - } - } -} - -fn runZigBuild(self: Builder, stderr_behavior: std.process.Child.StdIo) !std.process.Child { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - const arena_allocator = arena.allocator(); - - const args = try self.buildArgs(arena_allocator); - var child = std.process.Child.init(args, arena_allocator); - child.stderr_behavior = stderr_behavior; - try child.spawn(); - return child; -} - -fn buildArgs(self: Builder, arena: std.mem.Allocator) ![]const []const u8 { - var argv = std.ArrayList([]const u8).init(arena); - try argv.ensureTotalCapacity(self.steps.len + self.zig_build_args.len + 6); - argv.appendAssumeCapacity(try arena.dupe(u8, self.zig_path)); - argv.appendAssumeCapacity("build"); - - for (self.steps) |step| { - argv.appendAssumeCapacity(try arena.dupe(u8, step)); - } - - argv.appendAssumeCapacity("--color"); - argv.appendAssumeCapacity("on"); - argv.appendAssumeCapacity(try std.fmt.allocPrint(arena, "-Doptimize={s}", .{@tagName(self.optimize)})); - if (self.target) |target| { - argv.appendAssumeCapacity(try std.fmt.allocPrint(arena, "-Dtarget={s}", .{try target.toZigTriple(arena)})); - } - - for (self.zig_build_args) |arg| { - argv.appendAssumeCapacity(try arena.dupe(u8, arg)); - } - - return try argv.toOwnedSlice(); -} - -fn watch(self: *Builder) void { - _ = self; - // TODO: use std.fs.Watch once async implemented -} - -const path_handlers = std.ComptimeStringMap(*const fn (*Builder, std.net.Stream) void, .{ - .{ "/", struct { - fn h(self: *Builder, stream: std.net.Stream) void { - sendData(stream, .ok, .close, mime_map.get(".html").?, self.www_index_html); - } - }.h }, - .{ "/notify", struct { - fn h(builder: *Builder, stream: std.net.Stream) void { - sendData(stream, .ok, .keep_alive, "text/event-stream", null); - builder.subscribers.append(allocator, stream) catch { - stream.close(); - return; - }; - builder.notify(stream); - } - }.h }, - .{ "/wasmserve.js", struct { - fn h(_: *Builder, stream: std.net.Stream) void { - sendData(stream, .ok, .close, mime_map.get(".js").?, @"www/wasmserve.js"); - } - }.h }, - .{ "/ansi_to_html.js", struct { - fn h(_: *Builder, stream: std.net.Stream) void { - sendData(stream, .ok, .close, mime_map.get(".js").?, @"www/ansi_to_html.js"); - } - }.h }, - .{ "/favicon.ico", struct { - fn h(_: *Builder, stream: std.net.Stream) void { - sendData(stream, .ok, .close, mime_map.get(".ico").?, @"www/favicon.ico"); - } - }.h }, -}); - -fn sendData( - stream: std.net.Stream, - status: std.http.Status, - connection: std.http.Connection, - content_type: []const u8, - data: ?[]const u8, -) void { - if (data) |d| { - stream.writer().print( - "HTTP/1.1 {d} {s}\r\n" ++ - "Connection: {s}\r\n" ++ - "Content-Length: {d}\r\n" ++ - "Content-Type: {s}\r\n" ++ - "\r\n{s}", - .{ - @intFromEnum(status), - status.phrase() orelse "N/A", - switch (connection) { - .close => "close", - .keep_alive => "keep-alive", - }, - d.len, - content_type, - d, - }, - ) catch { - stream.close(); - return; - }; - } else { - stream.writer().print( - "HTTP/1.1 {d} {s}\r\n" ++ - "Connection: {s}\r\n" ++ - "Content-Type: {s}\r\n" ++ - "\r\n", - .{ - @intFromEnum(status), - status.phrase() orelse "N/A", - switch (connection) { - .close => "close", - .keep_alive => "keep-alive", - }, - content_type, - }, - ) catch { - stream.close(); - return; - }; - } -} - -fn handleConn(self: *Builder, conn: std.net.StreamServer.Connection) void { - errdefer { - sendError(conn.stream, .internal_server_error); - conn.stream.close(); - } - - var buf: [2048]u8 = undefined; - const first_line = conn.stream.reader().readUntilDelimiter(&buf, '\n') catch |err| { - defer conn.stream.close(); - return switch (err) { - error.StreamTooLong => sendError(conn.stream, .uri_too_long), - else => sendError(conn.stream, .bad_request), - }; - }; - var first_line_iter = std.mem.splitScalar(u8, first_line, ' '); - _ = first_line_iter.next(); // skip method - if (first_line_iter.next()) |uri_str| { - const uri = std.Uri.parseWithoutScheme(uri_str) catch { - defer conn.stream.close(); - return sendError(conn.stream, .bad_request); - }; - - const handler = path_handlers.get(uri.path) orelse { - // no handlers found. search in files - const rel_path = uri.path[1..]; - - const ext = std.fs.path.extension(rel_path); - const file_mime = mime_map.get(ext) orelse "text/plain"; - - const out_dir = std.fs.cwd().openDir(out_dir_path, .{}) catch |err| { - std.log.err("cannot open '{s}': {s}", .{ out_dir_path, @errorName(err) }); - std.os.exit(1); - }; - - const file = out_dir.openFile(rel_path, .{}) catch |err| { - if (err == error.FileNotFound) { - sendError(conn.stream, .not_found); - } else { - sendError(conn.stream, .internal_server_error); - } - return; - }; - defer file.close(); - - const file_size = file.getEndPos() catch { - sendError(conn.stream, .internal_server_error); - return; - }; - - conn.stream.writer().print( - "HTTP/1.1 200 OK\r\n" ++ - "Connection: close\r\n" ++ - "Content-Length: {d}\r\n" ++ - "Content-Type: {s}\r\n" ++ - "\r\n", - .{ file_size, file_mime }, - ) catch return; - std.fs.File.writeFileAll(.{ .handle = conn.stream.handle }, file, .{}) catch return; - - return; - }; - handler(self, conn.stream); - } else { - defer conn.stream.close(); - return sendError(conn.stream, .bad_request); - } -} - -fn sendError(stream: std.net.Stream, status: std.http.Status) void { - sendData(stream, status, .close, mime_map.get(".txt").?, status.phrase() orelse "N/A"); -} - -fn notify(self: *Builder, stream: std.net.Stream) void { - stream.writer().print("event: {s}\n", .{@tagName(self.status)}) catch { - stream.close(); - return; - }; - switch (self.status) { - .compile_error => |msg| { - var lines = std.mem.splitScalar(u8, msg, '\n'); - while (lines.next()) |line| { - stream.writer().print("data: {s}\n", .{line}) catch { - stream.close(); - return; - }; - } - }, - .built, .building, .stopped => {}, - } - _ = stream.write("\n") catch { - stream.close(); - return; - }; -} - -fn compile(self: *Builder) void { - std.log.info("building...", .{}); - - var child = self.runZigBuild(.Pipe) catch unreachable; - - const stderr = child.stderr.?.reader().readAllAlloc( - allocator, - std.math.maxInt(usize), - ) catch @panic("OOM"); - - std.io.getStdErr().writeAll(stderr) catch unreachable; - - const term = child.wait() catch unreachable; - if (term == .Exited and term.Exited == 0) { - allocator.free(stderr); - self.status = .built; - std.log.info("built", .{}); - } else if (term == .Exited and term.Exited == 1) { - std.log.warn("compile error", .{}); - self.status = .{ .compile_error = stderr }; - } else { - allocator.free(stderr); - self.status = .stopped; - std.log.warn("the build process has stopped unexpectedly", .{}); - } - - for (self.subscribers.items) |sub| { - self.notify(sub); - } -} diff --git a/src/editor/Builder/mime.zig b/src/editor/Builder/mime.zig deleted file mode 100644 index a4222d9e..00000000 --- a/src/editor/Builder/mime.zig +++ /dev/null @@ -1,59 +0,0 @@ -const std = @import("std"); - -pub const mime_map = std.ComptimeStringMap([]const u8, .{ - .{ ".aac", "audio/aac" }, - .{ ".avif", "image/avif" }, - .{ ".avi", "video/x-msvideo" }, - .{ ".bin", "application/octet-stream" }, - .{ ".bmp", "image/bmp" }, - .{ ".bz", "application/x-bzip" }, - .{ ".bz2", "application/x-bzip2" }, - .{ ".css", "text/css" }, - .{ ".csv", "text/csv" }, - .{ ".eot", "application/vnd.ms-fontobject" }, - .{ ".gz", "application/gzip" }, - .{ ".gif", "image/gif" }, - .{ ".htm", "text/html" }, - .{ ".html", "text/html" }, - .{ ".ico", "image/x-icon" }, - .{ ".ics", "text/calendar" }, - .{ ".jar", "application/java-archive" }, - .{ ".jpeg", "image/jpeg" }, - .{ ".jpg", "image/jpeg" }, - .{ ".js", "text/javascript" }, - .{ ".json", "application/json" }, - .{ ".md", "text/x-markdown" }, - .{ ".mjs", "text/javascript" }, - .{ ".mp3", "audio/mpeg" }, - .{ ".mp4", "video/mp4" }, - .{ ".mpeg", "video/mpeg" }, - .{ ".oga", "audio/ogg" }, - .{ ".ogv", "video/ogg" }, - .{ ".ogx", "application/ogg" }, - .{ ".opus", "audio/opus" }, - .{ ".otf", "font/otf" }, - .{ ".png", "image/png" }, - .{ ".pdf", "application/pdf" }, - .{ ".rar", "application/vnd.rar" }, - .{ ".rtf", "application/rtf" }, - .{ ".sh", "application/x-sh" }, - .{ ".svg", "image/svg+xml" }, - .{ ".tar", "application/x-tar" }, - .{ ".tif", ".tiff", "image/tiff" }, - .{ ".toml", "text/toml" }, - .{ ".ts", "video/mp2t" }, - .{ ".ttf", "font/ttf" }, - .{ ".txt", "text/plain" }, - .{ ".wasm", "application/wasm" }, - .{ ".wav", "audio/wav" }, - .{ ".weba", "audio/webm" }, - .{ ".webm", "video/webm" }, - .{ ".webp", "image/webp" }, - .{ ".woff", "font/woff" }, - .{ ".woff2", "font/woff2" }, - .{ ".yml", "application/x-yaml" }, - .{ ".xhtml", "application/xhtml+xml" }, - .{ ".xml", "application/xml" }, - .{ ".zip", "application/zip" }, - .{ ".7z", "application/x-7z-compressed" }, -}); diff --git a/src/editor/Builder/www/ansi_to_html.js b/src/editor/Builder/www/ansi_to_html.js deleted file mode 100644 index 16bd0529..00000000 --- a/src/editor/Builder/www/ansi_to_html.js +++ /dev/null @@ -1 +0,0 @@ -const defaults={fg:"#FFF",bg:"#000",newline:!1,stream:!1,colors:getDefaultColors()};function getDefaultColors(){const a={0:"#000",1:"#A00",2:"#0A0",3:"#A50",4:"#00A",5:"#A0A",6:"#0AA",7:"#AAA",8:"#555",9:"#F55",10:"#5F5",11:"#FF5",12:"#55F",13:"#F5F",14:"#5FF",15:"#FFF"};return range(0,5).forEach(b=>{range(0,5).forEach(c=>{range(0,5).forEach(d=>setStyleColor(b,c,d,a))})}),range(0,23).forEach(function(b){const c=toHexString(10*b+8);a[b+232]="#"+c+c+c}),a}function setStyleColor(a,c,d,e){const f=0b.length;)b="0"+b;return b}function toColorHexString(a){const b=[];for(const c of a)b.push(toHexString(c));return"#"+b.join("")}function generateOutput(a,b,c,d){let e;return"text"===b?e=pushText(c,d):"display"===b?e=handleDisplay(a,c,d):"xterm256Foreground"===b?e=pushForegroundColor(a,d.colors[c]):"xterm256Background"===b?e=pushBackgroundColor(a,d.colors[c]):"rgb"==b&&(e=handleRgb(a,c)),e}function handleRgb(a,b){b=b.substring(2).slice(0,-1);const c=+b.substr(0,2),d=b.substring(5).split(";"),e=d.map(function(a){return("0"+(+a).toString(16)).substr(-2)}).join("");return pushStyle(a,(38==c?"color:#":"background-color:#")+e)}function handleDisplay(a,b,c){b=parseInt(b,10);const d={"-1":()=>"
",0:()=>a.length&&resetStyles(a),1:()=>pushTag(a,"b"),3:()=>pushTag(a,"i"),4:()=>pushTag(a,"u"),8:()=>pushStyle(a,"display:none"),9:()=>pushTag(a,"strike"),22:()=>pushStyle(a,"font-weight:normal;text-decoration:none;font-style:normal"),23:()=>closeTag(a,"i"),24:()=>closeTag(a,"u"),39:()=>pushForegroundColor(a,c.fg),49:()=>pushBackgroundColor(a,c.bg),53:()=>pushStyle(a,"text-decoration:overline")};let e;return d[b]?e=d[b]():4b?e=pushTag(a,"blink"):29b?e=pushForegroundColor(a,c.colors[b-30]):39b?e=pushBackgroundColor(a,c.colors[b-40]):89b?e=pushForegroundColor(a,c.colors[8+(b-90)]):99b&&(e=pushBackgroundColor(a,c.colors[8+(b-100)])),e}function resetStyles(a){const b=a.slice(0);return a.length=0,b.reverse().map(function(a){return""}).join("")}function range(a,b){const c=[];for(let d=a;d<=b;d++)c.push(d);return c}function notCategory(a){return function(b){return(null===a||b.category!==a)&&"all"!==a}}function categoryForCode(a){a=parseInt(a,10);let b=null;return 0===a?b="all":1===a?b="bold":2a?b="underline":4a?b="blink":8===a?b="hide":9===a?b="strike":29a||39===a||89a?b="foreground-color":(39a||49===a||99a)&&(b="background-color"),b}function pushText(a){return a}function pushTag(a,b,c){return c||(c=""),a.push(b),`<${b}${c?` style="${c}"`:""}>`}function pushStyle(a,b){return pushTag(a,"span",b)}function pushForegroundColor(a,b){return pushTag(a,"span","color:"+b)}function pushBackgroundColor(a,b){return pushTag(a,"span","background-color:"+b)}function closeTag(a,b){let c;if(a.slice(-1)[0]===b&&(c=a.pop()),c)return""}function tokenize(a,b,c){function d(){return""}function e(a){return b.newline?c("display",-1):c("text",a),""}function f(b,c){c>h&&g||(g=!1,a=a.replace(b.pattern,b.sub))}let g=!1;const h=3,j=[{pattern:/^\x08+/,sub:d},{pattern:/^\x1b\[[012]?K/,sub:d},{pattern:/^\x1b\[\(B/,sub:d},{pattern:/^\x1b\[[34]8;2;\d+;\d+;\d+m/,sub:function(a){return c("rgb",a),""}},{pattern:/^\x1b\[38;5;(\d+)m/,sub:function(a,b){return c("xterm256Foreground",b),""}},{pattern:/^\x1b\[48;5;(\d+)m/,sub:function(a,b){return c("xterm256Background",b),""}},{pattern:/^\n/,sub:e},{pattern:/^\r+\n/,sub:e},{pattern:/^\r/,sub:e},{pattern:/^\x1b\[((?:\d{1,3};?)+|)m/,sub:function(a,b){g=!0,0===b.trim().length&&(b="0"),b=b.trimRight(";").split(";");for(const d of b)c("display",d);return""}},{pattern:/^\x1b\[\d?J/,sub:d},{pattern:/^\x1b\[\d{0,3};\d{0,3}f/,sub:d},{pattern:/^\x1b\[?[\d;]{0,3}/,sub:d},{pattern:/^(([^\x1b\x08\r\n])+)/,sub:function(a){return c("text",a),""}}],k=[];let{length:l}=a;outer:for(;0{const e=generateOutput(b,a.token,a.data,c);e&&d.push(e)}),tokenize(a.join(""),c,(a,e)=>{const f=generateOutput(b,a,e,c);f&&d.push(f),c.stream&&(this.stickyStack=updateStickyStack(this.stickyStack,a,e))}),b.length&&d.push(resetStyles(b)),d.join("")}}export default new Filter; \ No newline at end of file diff --git a/src/editor/Builder/www/favicon.ico b/src/editor/Builder/www/favicon.ico deleted file mode 100644 index 0f885fc5..00000000 Binary files a/src/editor/Builder/www/favicon.ico and /dev/null differ diff --git a/src/editor/Builder/www/index.html b/src/editor/Builder/www/index.html deleted file mode 100644 index 50cd435f..00000000 --- a/src/editor/Builder/www/index.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - {[app_name]s} - - - - - - - - \ No newline at end of file diff --git a/src/editor/Builder/www/wasmserve.js b/src/editor/Builder/www/wasmserve.js deleted file mode 100644 index 0c0781b3..00000000 --- a/src/editor/Builder/www/wasmserve.js +++ /dev/null @@ -1,68 +0,0 @@ -import ansi_to_html from "./ansi_to_html.js"; - -let evtSource = new EventSource("/notify"); - -function setup() { - evtSource.addEventListener("building", function (e) { - // TODO - }); - evtSource.addEventListener("built", function (e) { - window.location.reload(); - }); - evtSource.addEventListener("compile_error", function (e) { - createErrorScreen("An error occurred while building:", e.data) - }); - evtSource.addEventListener("stopped", function (e) { - createErrorScreen("The build process has stopped unexpectedly:", e.data) - }); -} - -function createErrorScreen(msg, data) { - if (document.getElementById("error-screen") == null) { - let es = document.createElement("div"); - es.id = "error-screen"; - es.style.cssText = error_screen_css; - let h2 = document.createElement("h2"); - let pre = document.createElement("pre"); - h2.textContent = msg; - h2.style.cssText = error_screen_h2_css; - pre.innerHTML = ansi_to_html.toHtml(data); - pre.style.cssText = error_screen_pre_css; - es.appendChild(h2); - es.appendChild(pre); - document.body.appendChild(es); - - // atm ANSI escape codes only works in chromium based browsers - if (!!window.chrome) - console.log(data); - } else { - document.getElementById("error-screen"). - getElementsByTagName("pre").innerHTML = ansi_to_html.toHtml(data); - } -} - -const error_screen_css = - "position: absolute;" + - "width: 100vw;" + - "height: 100vh;" + - "top: 0;" + - "left: 0;" + - "background: rgba(0, 0, 0, 0.8);" + - "font-family: system-ui, monospace;" + - "font-size: 16pt;" + - "padding: 20px;" + - "box-sizing: border-box;" + - "color: white;" + - "z-index: 1;"; - -const error_screen_h2_css = "margin-top: 0;"; - -const error_screen_pre_css = - "border-top: 8px solid #A00;" + - "padding: 10px;" + - "background: black;" + - "font-size: 12pt;" + - "white-space: pre-wrap;" + - "overflow: hidden;"; - -export default setup; \ No newline at end of file diff --git a/src/editor/app.zig b/src/editor/app.zig deleted file mode 100755 index 58270dfb..00000000 --- a/src/editor/app.zig +++ /dev/null @@ -1,245 +0,0 @@ -const std = @import("std"); -const mach = @import("mach"); -const core = mach.core; -const gpu = mach.gpu; - -pub const name = .editor; -pub const modules = .{ mach.Engine, @This() }; -pub const App = mach.App; - -const UniformBufferObject = struct { - resolution: @Vector(2, f32), - time: f32, -}; - -var gpa = std.heap.GeneralPurposeAllocator(.{}){}; -const allocator = gpa.allocator(); - -timer: mach.Timer, -pipeline: *gpu.RenderPipeline, -queue: *gpu.Queue, -uniform_buffer: *gpu.Buffer, -bind_group: *gpu.BindGroup, - -fragment_shader_file: std.fs.File, -fragment_shader_code: [:0]const u8, -last_mtime: i128, - -pub fn init(editor: *mach.Mod(.editor)) !void { - core.setTitle("Mach editor"); - - var fragment_file: std.fs.File = undefined; - var last_mtime: i128 = undefined; - - // TODO: there is no guarantee we are in the mach project root - if (std.fs.cwd().openFile("src/editor/frag.wgsl", .{ .mode = .read_only })) |file| { - fragment_file = file; - if (file.stat()) |stat| { - last_mtime = stat.mtime; - } else |err| { - std.debug.print("Something went wrong when attempting to stat file: {}\n", .{err}); - return; - } - } else |e| { - std.debug.print("Something went wrong when attempting to open file: {}\n", .{e}); - return; - } - var code = try fragment_file.readToEndAllocOptions(allocator, std.math.maxInt(u16), null, 1, 0); - - const queue = core.device.getQueue(); - - // We need a bgl to bind the UniformBufferObject, but it is also needed for creating - // the RenderPipeline, so we pass it to recreatePipeline as a pointer - var bgl: *gpu.BindGroupLayout = undefined; - const pipeline = recreatePipeline(code, &bgl); - - const uniform_buffer = core.device.createBuffer(&.{ - .usage = .{ .copy_dst = true, .uniform = true }, - .size = @sizeOf(UniformBufferObject), - .mapped_at_creation = .false, - }); - const bind_group = core.device.createBindGroup( - &gpu.BindGroup.Descriptor.init(.{ - .layout = bgl, - .entries = &.{ - gpu.BindGroup.Entry.buffer(0, uniform_buffer, 0, @sizeOf(UniformBufferObject)), - }, - }), - ); - - editor.state.timer = try mach.Timer.start(); - - editor.state.pipeline = pipeline; - editor.state.queue = queue; - editor.state.uniform_buffer = uniform_buffer; - editor.state.bind_group = bind_group; - - editor.state.fragment_shader_file = fragment_file; - editor.state.fragment_shader_code = code; - editor.state.last_mtime = last_mtime; - - bgl.release(); -} - -pub fn deinit(editor: *mach.Mod(.editor)) !void { - defer _ = gpa.deinit(); - - editor.state.fragment_shader_file.close(); - allocator.free(editor.state.fragment_shader_code); - - editor.state.uniform_buffer.release(); - editor.state.bind_group.release(); -} - -pub fn tick( - engine: *mach.Mod(.engine), - editor: *mach.Mod(.editor), -) !void { - var iter = core.pollEvents(); - while (iter.next()) |event| { - switch (event) { - .key_press => |ev| { - if (ev.key == .space) return engine.send(.exit, .{}); - }, - .close => return engine.send(.exit, .{}), - else => {}, - } - } - - if (editor.state.fragment_shader_file.stat()) |stat| { - if (editor.state.last_mtime < stat.mtime) { - std.log.info("The fragment shader has been changed", .{}); - editor.state.last_mtime = stat.mtime; - editor.state.fragment_shader_file.seekTo(0) catch unreachable; - editor.state.fragment_shader_code = editor.state.fragment_shader_file.readToEndAllocOptions(allocator, std.math.maxInt(u32), null, 1, 0) catch |err| { - std.log.err("Err: {}", .{err}); - return engine.send(.exit, .{}); - }; - editor.state.pipeline = recreatePipeline(editor.state.fragment_shader_code, null); - } - } else |err| { - std.log.err("Something went wrong when attempting to stat file: {}\n", .{err}); - } - - const back_buffer_view = core.swap_chain.getCurrentTextureView().?; - const color_attachment = gpu.RenderPassColorAttachment{ - .view = back_buffer_view, - .clear_value = std.mem.zeroes(gpu.Color), - .load_op = .clear, - .store_op = .store, - }; - - const encoder = core.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassDescriptor.init(.{ - .color_attachments = &.{color_attachment}, - }); - - const time = editor.state.timer.read() / @as(f32, std.time.ns_per_s); - const ubo = UniformBufferObject{ - .resolution = .{ @as(f32, @floatFromInt(core.descriptor.width)), @as(f32, @floatFromInt(core.descriptor.height)) }, - .time = time, - }; - encoder.writeBuffer(editor.state.uniform_buffer, 0, &[_]UniformBufferObject{ubo}); - - const pass = encoder.beginRenderPass(&render_pass_info); - pass.setPipeline(editor.state.pipeline); - pass.setBindGroup(0, editor.state.bind_group, &.{0}); - pass.draw(3, 1, 0, 0); - pass.end(); - pass.release(); - - var command = encoder.finish(null); - encoder.release(); - - editor.state.queue.submit(&[_]*gpu.CommandBuffer{command}); - command.release(); - core.swap_chain.present(); - back_buffer_view.release(); -} - -fn recreatePipeline(fragment_shader_code: [:0]const u8, bgl: ?**gpu.BindGroupLayout) *gpu.RenderPipeline { - const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl")); - defer vs_module.release(); - - // Check wether the fragment shader code compiled successfully, if not - // print the validation layer error and show a black screen - core.device.pushErrorScope(.validation); - var fs_module = core.device.createShaderModuleWGSL("fragment shader", fragment_shader_code); - var error_occurred: bool = false; - // popErrorScope() returns always true, (unless maybe it fails to capture the error scope?) - _ = core.device.popErrorScope(&error_occurred, struct { - inline fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void { - if (typ != .no_error) { - std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message}); - ctx.* = true; - } - } - }.callback); - if (error_occurred) { - fs_module = core.device.createShaderModuleWGSL( - "black_screen_frag.wgsl", - @embedFile("black_screen_frag.wgsl"), - ); - } - defer fs_module.release(); - - const blend = gpu.BlendState{}; - const color_target = gpu.ColorTargetState{ - .format = core.descriptor.format, - .blend = &blend, - .write_mask = gpu.ColorWriteMaskFlags.all, - }; - const fragment = gpu.FragmentState.init(.{ - .module = fs_module, - .entry_point = "main", - .targets = &.{color_target}, - }); - - const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .fragment = true }, .uniform, true, 0); - // bgl is needed outside, for the creation of the uniform_buffer in main - const bgl_tmp = core.device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{ - .entries = &.{bgle}, - })); - defer { - // In frame we don't need to use bgl, so we can release it inside this function, else we pass bgl - if (bgl == null) { - bgl_tmp.release(); - } else { - bgl.?.* = bgl_tmp; - } - } - - const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl_tmp}; - const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{ - .bind_group_layouts = &bind_group_layouts, - })); - defer pipeline_layout.release(); - - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .fragment = &fragment, - .layout = pipeline_layout, - .vertex = gpu.VertexState.init(.{ - .module = vs_module, - .entry_point = "main", - }), - }; - - // Create the render pipeline. Even if the shader compilation succeeded, this could fail if the - // shader is missing a `main` entrypoint. - core.device.pushErrorScope(.validation); - const pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - // popErrorScope() returns always true, (unless maybe it fails to capture the error scope?) - _ = core.device.popErrorScope(&error_occurred, struct { - inline fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void { - if (typ != .no_error) { - std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message}); - ctx.* = true; - } - } - }.callback); - if (error_occurred) { - // Retry with black_screen_frag which we know will work. - return recreatePipeline(@embedFile("black_screen_frag.wgsl"), bgl); - } - return pipeline; -} diff --git a/src/editor/black_screen_frag.wgsl b/src/editor/black_screen_frag.wgsl deleted file mode 100755 index 00c5e470..00000000 --- a/src/editor/black_screen_frag.wgsl +++ /dev/null @@ -1,11 +0,0 @@ -struct UniformBufferObject { - time: f32, - resolution: vec2, -} -@group(0) @binding(0) var ubo : UniformBufferObject; - -@fragment fn main( - @location(0) uv : vec2 -) -> @location(0) vec4 { - return vec4( 0.0, 0.0, 0.0, 1.0); -} diff --git a/src/editor/frag.wgsl b/src/editor/frag.wgsl deleted file mode 100755 index 7af70d54..00000000 --- a/src/editor/frag.wgsl +++ /dev/null @@ -1,94 +0,0 @@ -struct UniformBufferObject { - resolution: vec2, - time: f32, -} -@group(0) @binding(0) var ubo : UniformBufferObject; - -fn getDist(p:vec3) -> f32{ - let dist_from_center:f32 = 2.*sin(ubo.time * 3.); - let rotation_speed:f32 = 6.; - - let sphere1 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 0. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 0. * 2. / 3.),1.); - let sphere2 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 1. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 1. * 2. / 3.),1.); - let sphere3 = vec4(dist_from_center*cos(rotation_speed*ubo.time + 3.14159 * 2. * 2. / 3.),1.1, dist_from_center*sin(rotation_speed*ubo.time + 3.14159 + 3.14159 * 2. * 2. / 3.),1.); - - let sphere1_dist:f32 = length(p - sphere1.xyz) - sphere1.w; - let sphere2_dist:f32 = length(p - sphere2.xyz) - sphere2.w; - let sphere3_dist:f32 = length(p - sphere3.xyz) - sphere3.w; - let plane_dist = p.y; - - return min(min(min(sphere1_dist,sphere2_dist),sphere3_dist),plane_dist); -} - -fn rayMarch(ro:vec3, rd:vec3) -> f32{ - let MAX_STEPS:i32 = 100; - let MAX_DIST:f32 = 100.0; - let SURF_DIST:f32 = 0.01; - var d:f32 = 0.0; - - var i: i32 = 0; - loop { - if(i >= MAX_STEPS){ - break; - } - - let p = ro + rd * d; - let ds = getDist(p); - d = d + ds; - if(d > MAX_DIST || ds <= SURF_DIST){ - break; - } - - i = i + 1; - } - return d; -} - -fn getNormal(p:vec3) -> vec3{ - let d = getDist(p); - let e = vec2(0.1,0.0); - - // We can find the normal using the points around the hit point - let n = d - vec3( - getDist(p-e.xyy), - getDist(p-e.yxy), - getDist(p-e.yyx) - ); - - return normalize(n); -} - -fn getLight(p:vec3) -> f32{ - let SURF_DIST:f32 = .01; - - let light_pos = vec3(0.,5.,0.); - let l = normalize(light_pos - p) * 1.; - let n = getNormal(p); - - var dif = clamp(dot(n,l),.0,1.); - - let d = rayMarch(p + n * SURF_DIST * 2.,l); - if(d -) -> @location(0) vec4 { - let aspect = ubo.resolution / min(ubo.resolution.x,ubo.resolution.y); - let tmp_uv = (uv - vec2(0.5,0.5)) * aspect * 2.0; - var col = vec3(0.0); - - let r_origin = vec3(4.0,3.,.0); - let r_dir = normalize(vec3(-1.0,tmp_uv.y,tmp_uv.x)); - let d = rayMarch(r_origin,r_dir); - col = vec3(d / 8.); - let p = r_origin + r_dir * d; - let diff = getLight(p); - - col = vec3(0.0, diff, 0.0); - return vec4(col,0.0); -} diff --git a/src/editor/main.zig b/src/editor/main.zig deleted file mode 100644 index a9b51fc8..00000000 --- a/src/editor/main.zig +++ /dev/null @@ -1,209 +0,0 @@ -//! The 'mach' CLI and engine editor - -// Check that the user's app matches the required interface. -comptime { - if (!@import("builtin").is_test) @import("core").AppInterface(@import("app")); -} - -const std = @import("std"); -const builtin = @import("builtin"); - -// Forward "app" declarations into our namespace, such that @import("root").foo works as expected. -pub usingnamespace @import("app"); -const App = @import("app").App; -const core = @import("core"); -const gpu = core.gpu; - -const Builder = @import("Builder.zig"); -const Target = @import("target.zig").Target; - -pub const GPUInterface = gpu.dawn.Interface; - -const default_zig_path = "zig"; - -var args: []const [:0]u8 = undefined; -var arg_i: usize = 1; -var gpa = std.heap.GeneralPurposeAllocator(.{}){}; -pub const allocator = gpa.allocator(); - -pub fn main() !void { - defer _ = gpa.deinit(); - - args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - if (args.len == 1) { - gpu.Impl.init(); - _ = gpu.Export(GPUInterface); - - var app: App = undefined; - try app.init(); - defer app.deinit(); - - while (true) { - if (try core.update(&app)) return; - } - } - - if (std.mem.eql(u8, args[arg_i], "build")) { - arg_i += 1; - - var builder = Builder{}; - var steps = std.ArrayList([]const u8).init(allocator); - var build_args = std.ArrayList([]const u8).init(allocator); - - if (std.mem.eql(u8, args[arg_i], "help") or std.mem.eql(u8, args[arg_i], "--help") or std.mem.eql(u8, args[arg_i], "-h")) { - try printHelp(.build); - std.os.exit(1); - } - - while (arg_i < args.len) : (arg_i += 1) { - if (argOption("-zig-path")) |value| { - builder.zig_path = value; - } else if (std.mem.eql(u8, args[arg_i], "--serve")) { - if (builder.target == null) builder.target = .wasm32; - if (builder.target.? != .wasm32) { - std.log.err("--serve requires -target=wasm32", .{}); - try printHelp(.build); - std.os.exit(1); - } - builder.serve = true; - } else if (argOption("-target")) |value| { - builder.target = Target.parse(value) orelse { - std.log.err("invalid target '{s}'", .{args[arg_i]}); - try printHelp(.build); - std.os.exit(1); - }; - } else if (argOption("-listen-port")) |value| { - builder.listen_port = std.fmt.parseInt(u16, value, 10) catch { - std.log.err("invalid port '{s}'", .{args[arg_i]}); - try printHelp(.build); - std.os.exit(1); - }; - } else if (argOption("-watch-path")) |value| { - var paths = std.mem.splitScalar(u8, value, ','); - builder.watch_paths = try allocator.alloc([]const u8, std.mem.count(u8, value, ",") + 1); - for (0..255) |i| { - const path = paths.next() orelse break; - builder.watch_paths.?[i] = std.mem.trim(u8, path, &std.ascii.whitespace); - } - } else if (argOption("-optimize")) |value| { - builder.optimize = std.meta.stringToEnum(std.builtin.OptimizeMode, value) orelse { - std.log.err("invalid optimize mode '{s}'", .{args[arg_i]}); - try printHelp(.build); - std.os.exit(1); - }; - } else if (std.mem.eql(u8, args[arg_i], "--")) { - arg_i += 1; - while (arg_i < args.len) : (arg_i += 1) { - try build_args.append(args[arg_i]); - } - } else { - try steps.append(args[arg_i]); - } - } - - builder.steps = try steps.toOwnedSlice(); - builder.zig_build_args = try build_args.toOwnedSlice(); - return builder.run(); - } else if (std.mem.eql(u8, args[arg_i], "help") or std.mem.eql(u8, args[arg_i], "--help") or std.mem.eql(u8, args[arg_i], "-h")) { - arg_i += 1; - var subcommand = SubCommand.help; - - if (arg_i < args.len) { - if (std.mem.eql(u8, args[arg_i], "build")) { - subcommand = .build; - } else { - std.log.err("unknown command name '{s}'", .{args[arg_i]}); - try printHelp(.help); - std.os.exit(1); - } - } - return printHelp(subcommand); - } else { - std.log.err("invalid command '{s}'", .{args[arg_i]}); - try printHelp(.help); - std.os.exit(1); - } -} - -pub const SubCommand = enum { - build, - help, -}; - -fn printHelp(subcommand: SubCommand) !void { - const stdout = std.io.getStdOut(); - switch (subcommand) { - .build => { - try stdout.writeAll( - \\Usage: - \\ mach build [steps] [options] [-- [zig-build-options]] - \\ - \\General Options: - \\ - \\ -zig-path [path] Override path to zig binary - \\ - \\ -target [target] The CPU architecture and OS to build for - \\ Default is native target - \\ Supported targets: - \\ linux-x86_64, linux-aarch64, - \\ macos-x86_64, macos-aarch64, - \\ windows-x86_64, windows-aarch64, - \\ wasm32, - \\ - \\ -optimize [optimize] Prioritize performance, safety, or binary size - \\ Default is Debug - \\ Supported values: - \\ Debug - \\ ReleaseSafe - \\ ReleaseFast - \\ ReleaseSmall - \\ - \\Serve Options: - \\ - \\ --serve Starts a development server - \\ for testing WASM applications/games - \\ - \\ -listen-port [port] The development server port - \\ - \\ -watch-path [paths] Watches for changes in specified directory - \\ and automatically builds and reloads - \\ development server - \\ Separate each path with comma (,) - \\ - \\ - ); - }, - .help => { - try stdout.writeAll( - \\Usage: - \\ mach [command] - \\ - \\Commands: - \\ build Build current project - \\ help Print this mesage or the help of the given command - \\ - \\ - ); - }, - } -} - -pub fn argOption(name: []const u8) ?[]const u8 { - const cmd_arg = args[arg_i]; - if (std.mem.startsWith(u8, cmd_arg, name)) { - if (cmd_arg.len > name.len + 1 and cmd_arg[name.len] == '=') { - return cmd_arg[name.len + 1 ..]; - } else if (cmd_arg.len == name.len) { - arg_i += 1; - if (arg_i < args.len) { - return args[arg_i]; - } else { - std.log.err("expected value after '{s}' option", .{cmd_arg}); - std.os.exit(1); - } - } - } - return null; -} diff --git a/src/editor/target.zig b/src/editor/target.zig deleted file mode 100644 index e09b8980..00000000 --- a/src/editor/target.zig +++ /dev/null @@ -1,52 +0,0 @@ -const std = @import("std"); -const allocator = @import("main.zig").allocator; - -pub const Target = enum { - @"linux-x86_64", - @"linux-aarch64", - @"macos-x86_64", - @"macos-aarch64", - @"windows-x86_64", - @"windows-aarch64", - wasm32, - - // TODO - // android, - // ios, - - pub fn parse(str: []const u8) ?Target { - return if (std.mem.eql(u8, str, "linux")) - .@"linux-x86_64" - else if (std.mem.eql(u8, str, "windows")) - .@"windows-x86_64" - else if (std.mem.eql(u8, str, "macos")) - .@"macos-aarch64" - else if (std.mem.eql(u8, str, "wasm")) - .wasm32 - else - std.meta.stringToEnum(Target, str) orelse return null; - } - - pub fn toZigTriple(self: Target, allocator2: std.mem.Allocator) ![]const u8 { - const zig_target = std.zig.CrossTarget{ - .cpu_arch = switch (self) { - .@"linux-x86_64", - .@"macos-x86_64", - .@"windows-x86_64", - => .x86_64, - .@"linux-aarch64", - .@"macos-aarch64", - .@"windows-aarch64", - => .aarch64, - .wasm32 => .wasm32, - }, - .os_tag = switch (self) { - .@"linux-x86_64", .@"linux-aarch64" => .linux, - .@"macos-x86_64", .@"macos-aarch64" => .macos, - .@"windows-x86_64", .@"windows-aarch64" => .windows, - .wasm32 => .freestanding, - }, - }; - return zig_target.zigTriple(allocator2); - } -}; diff --git a/src/editor/vert.wgsl b/src/editor/vert.wgsl deleted file mode 100755 index 6f972316..00000000 --- a/src/editor/vert.wgsl +++ /dev/null @@ -1,23 +0,0 @@ -struct VertexOut { - @builtin(position) position_clip : vec4, - @location(0) frag_uv : vec2, -} - -@vertex fn main(@builtin(vertex_index) index : u32) -> VertexOut { - var pos = array, 3>( - vec2(-1.0, -1.0), - vec2( 3.0, -1.0), - vec2(-1.0, 3.0), - ); - - var uv = array, 3>( - vec2(0.0, 0.0), - vec2(2.0, 0.0), - vec2(0.0, 2.0), - ); - - var output : VertexOut; - output.position_clip = vec4(pos[index], 0.0, 1.0); - output.frag_uv = uv[index]; - return output; -}