Add README and simplify ViewEngine API

- ViewEngine.init() no longer requires allocator
- render() and renderTpl() accept allocator parameter
- Remove deinit() - no resources to clean up
- Remove unused parse/renderDoc methods
- Add memory management guidance to runtime.zig
- Clean up unused imports and options
This commit is contained in:
2026-01-17 23:59:22 +05:30
parent 1fff91d7d9
commit 458de03c02
19 changed files with 1036 additions and 366 deletions

View File

@@ -0,0 +1,170 @@
const std = @import("std");
const pugz = @import("pugz");
const friends_tpl =
\\doctype html
\\html(lang="en")
\\ head
\\ meta(charset="UTF-8")
\\ title Friends
\\ body
\\ div.friends
\\ each friend in friends
\\ div.friend
\\ ul
\\ li Name: #{friend.name}
\\ li Balance: #{friend.balance}
\\ li Age: #{friend.age}
\\ li Address: #{friend.address}
\\ li Image:
\\ img(src=friend.picture)
\\ li Company: #{friend.company}
\\ li Email:
\\ a(href=friend.emailHref) #{friend.email}
\\ li About: #{friend.about}
\\ if friend.tags
\\ li Tags:
\\ ul
\\ each tag in friend.tags
\\ li #{tag}
\\ if friend.friends
\\ li Friends:
\\ ul
\\ each subFriend in friend.friends
\\ li #{subFriend.name} (#{subFriend.id})
;
const SubFriend = struct { id: i32, name: []const u8 };
const Friend = struct {
name: []const u8,
balance: []const u8,
age: i32,
address: []const u8,
picture: []const u8,
company: []const u8,
email: []const u8,
emailHref: []const u8,
about: []const u8,
tags: ?[]const []const u8,
friends: ?[]const SubFriend,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit();
const engine = pugz.ViewEngine.init(.{});
const friend_tags = &[_][]const u8{ "id", "amet", "non", "ut", "dolore", "commodo", "consequat" };
const sub_friends = &[_]SubFriend{
.{ .id = 0, .name = "Gates Lewis" },
.{ .id = 1, .name = "Britt Stokes" },
.{ .id = 2, .name = "Reed Wade" },
};
var friends_data: [100]Friend = undefined;
for (&friends_data, 0..) |*f, i| {
f.* = .{
.name = "Gardner Alvarez",
.balance = "$1,509.00",
.age = 30 + @as(i32, @intCast(i % 20)),
.address = "282 Lancaster Avenue, Bowden, Kansas, 666",
.picture = "http://placehold.it/32x32",
.company = "Dentrex",
.email = "gardneralvarez@dentrex.com",
.emailHref = "mailto:gardneralvarez@dentrex.com",
.about = "Minim elit tempor enim voluptate labore do non nisi sint nulla deserunt officia proident excepteur.",
.tags = friend_tags,
.friends = sub_friends,
};
}
const data = .{ .friends = &friends_data };
// Warmup
for (0..10) |_| {
_ = arena.reset(.retain_capacity);
_ = try engine.renderTpl(arena.allocator(), friends_tpl, data);
}
// Get output size
_ = arena.reset(.retain_capacity);
const output = try engine.renderTpl(arena.allocator(), friends_tpl, data);
const output_size = output.len;
// Profile render
const iterations: usize = 500;
var total_render: u64 = 0;
var timer = try std.time.Timer.start();
for (0..iterations) |_| {
_ = arena.reset(.retain_capacity);
timer.reset();
_ = try engine.renderTpl(arena.allocator(), friends_tpl, data);
total_render += timer.read();
}
const avg_render_us = @as(f64, @floatFromInt(total_render)) / @as(f64, @floatFromInt(iterations)) / 1000.0;
const total_ms = @as(f64, @floatFromInt(total_render)) / 1_000_000.0;
// Header
std.debug.print("\n", .{});
std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
std.debug.print("║ FRIENDS TEMPLATE CPU PROFILE ║\n", .{});
std.debug.print("╠══════════════════════════════════════════════════════════════╣\n", .{});
std.debug.print("║ Iterations: {d:<6} Output size: {d:<6} bytes ║\n", .{ iterations, output_size });
std.debug.print("╚══════════════════════════════════════════════════════════════╝\n\n", .{});
// Results
std.debug.print("┌────────────────────────────────────┬─────────────────────────┐\n", .{});
std.debug.print("│ Metric │ Value │\n", .{});
std.debug.print("├────────────────────────────────────┼─────────────────────────┤\n", .{});
std.debug.print("│ Total time │ {d:>10.1} ms │\n", .{total_ms});
std.debug.print("│ Avg per render │ {d:>10.1} µs │\n", .{avg_render_us});
std.debug.print("│ Renders/sec │ {d:>10.0} │\n", .{1_000_000.0 / avg_render_us});
std.debug.print("└────────────────────────────────────┴─────────────────────────┘\n", .{});
// Template complexity breakdown
std.debug.print("\n📋 Template Complexity:\n", .{});
std.debug.print(" • 100 friends (outer loop)\n", .{});
std.debug.print(" • 7 tags per friend (nested loop) = 700 tag iterations\n", .{});
std.debug.print(" • 3 sub-friends per friend (nested loop) = 300 sub-friend iterations\n", .{});
std.debug.print(" • Total loop iterations: 100 + 700 + 300 = 1,100\n", .{});
std.debug.print(" • ~10 interpolations per friend = 1,000+ variable lookups\n", .{});
std.debug.print(" • 2 conditionals per friend = 200 conditional evaluations\n", .{});
// Cost breakdown estimate
const loop_iterations: f64 = 1100;
const var_lookups: f64 = 1500; // approximate
std.debug.print("\n💡 Estimated Cost Breakdown (per render):\n", .{});
std.debug.print(" Total: {d:.1} µs\n", .{avg_render_us});
std.debug.print(" Per loop iteration: ~{d:.2} µs ({d:.0} iterations)\n", .{ avg_render_us / loop_iterations, loop_iterations });
std.debug.print(" Per variable lookup: ~{d:.3} µs ({d:.0} lookups)\n", .{ avg_render_us / var_lookups, var_lookups });
// Comparison
std.debug.print("\n📊 Comparison with Pug.js:\n", .{});
const pugjs_us: f64 = 55.0; // From benchmark: 110ms / 2000 = 55µs
std.debug.print(" Pug.js: {d:.1} µs/render\n", .{pugjs_us});
std.debug.print(" Pugz: {d:.1} µs/render\n", .{avg_render_us});
const ratio = avg_render_us / pugjs_us;
if (ratio > 1.0) {
std.debug.print(" Status: Pugz is {d:.1}x SLOWER\n", .{ratio});
} else {
std.debug.print(" Status: Pugz is {d:.1}x FASTER\n", .{1.0 / ratio});
}
std.debug.print("\nKey Bottlenecks (likely):\n", .{});
std.debug.print(" 1. Data conversion: Zig struct -> pugz.Value (comptime reflection)\n", .{});
std.debug.print(" 2. Variable lookup: HashMap get() for each interpolation\n", .{});
std.debug.print(" 3. AST traversal: Walking tree nodes vs Pug.js compiled JS functions\n", .{});
std.debug.print(" 4. Loop scope: Creating/clearing scope per loop iteration\n", .{});
std.debug.print("\nAlready optimized:\n", .{});
std.debug.print(" - Scope pooling (reuse hashmap capacity)\n", .{});
std.debug.print(" - Batched HTML escaping\n", .{});
std.debug.print(" - Arena allocator with retain_capacity\n", .{});
}