From 198235d20413f1e10515db430574a648bc67f7b7 Mon Sep 17 00:00:00 2001 From: adamws Date: Sun, 24 Nov 2024 14:51:06 +0100 Subject: [PATCH] wip: change rendering approach --- src/main.zig | 112 +++++++++++++++++++++++++---- src/win32.zig | 12 +--- src/x11.zig | 195 +++++++++++++++++++++++--------------------------- 3 files changed, 188 insertions(+), 131 deletions(-) diff --git a/src/main.zig b/src/main.zig index 406cc3f..f3aa21c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -104,7 +104,7 @@ const KeyOnScreen = struct { pressed: bool, }; -pub const KeyData = struct { +pub const KeyData = extern struct { pressed: bool, repeated: bool, keycode: u8, @@ -151,6 +151,49 @@ const CodepointBuffer = struct { } }; +const KeyDataProducer = struct { + reader: std.io.AnyReader, + frame_read: bool, + next_frame: usize = 0, + + pub fn init(reader: std.io.AnyReader) KeyDataProducer { + return .{ + .reader = reader, + .frame_read = true, + }; + } + + fn nextFrame(self: *KeyDataProducer) !usize { + if (self.frame_read) { + self.next_frame = try self.reader.readInt(usize, .little); + self.frame_read = false; + } + return self.next_frame; + } + + fn next(self: *KeyDataProducer) !KeyData { + const key_data = try self.reader.readStruct(KeyData); + self.frame_read = true; + return key_data; + } + + pub fn getDataForFrame(self: *KeyDataProducer, frame: usize) !?KeyData { + const next_frame = try self.nextFrame(); + if (next_frame == frame) { + return try self.next(); + } + return null; + } +}; + +fn getDataForFrame(frame: usize) !?KeyData { + if (key_data_producer) |*producer| { + return try producer.getDataForFrame(frame); + } + return null; +} + +var key_data_producer: ?KeyDataProducer = null; pub var app_state: AppState = undefined; // https://github.com/bits/UTF-8-Unicode-Test-Documents/blob/master/UTF-8_sequence_unseparated/utf8_sequence_0-0xfff_assigned_printable_unseparated.txt @@ -595,18 +638,18 @@ pub fn main() !void { } defer if (pixels) |p| allocator.free(p); - var thread: ?std.Thread = null; if (res.args.replay) |replay_file| { - // TODO: this will start processing events before rendering ready, add synchronization - const loop = res.args.@"replay-loop" != 0; - thread = try std.Thread.spawn(.{}, backend.producer, .{ &app_state, window_handle, replay_file, loop }); - } else { - // TODO: assign to thread var when close supported, join on this thread won't work now - _ = try std.Thread.spawn(.{}, backend.listener, .{ &app_state, window_handle, res.args.record }); + const file = try fs.cwd().openFile(replay_file, .{}); + //defer file.close(); + var buf_reader = std.io.bufferedReader(file.reader()); + const reader = buf_reader.reader(); + key_data_producer = KeyDataProducer.init(reader.any()); + } + + if (res.args.replay == null) { + // TODO: defer when close supported, join on this thread won't work now + _ = try std.Thread.spawn(.{}, backend.listener, .{ &app_state, window_handle }); } - defer if (thread) |t| { - t.join(); - }; var keycap_texture = blk: { const theme = Theme.fromString(app_config.data.theme) orelse unreachable; @@ -664,6 +707,19 @@ pub fn main() !void { var drag_reference_position = rl.getWindowPosition(); + // TODO: use buffered writer, to do that we must gracefully handle this thread exit, + // otherwise there is no good place to ensure writer flush + // TODO: support full file path + var event_file: ?fs.File = null; + if (res.args.record) |record_file| { + event_file = try cwd.createFile(record_file, .{}); + } + defer if(event_file) |value| value.close(); + + //const event_file_writer = event_file.writer(); + + var frame: usize = 0; + while (!exit_window) { if (rl.windowShouldClose()) { exit_window = true; @@ -749,7 +805,36 @@ pub fn main() !void { }; } + // replay + if (getDataForFrame(frame)) |data| { + if (data) |value| { + if (value.string[0] != 0) { + // update only for keys which produe output, + // this will not include modifiers + app_state.last_char_timestamp = std.time.timestamp(); + } + std.debug.print("Push {any}\n", .{value}); + _ = app_state.keys.push(value); + } + } else |err| switch (err) { + error.EndOfStream => { + std.debug.print("END OF STREAM\n", .{}); + exit_window = true; + }, + else => return err, + } + if (app_state.keys.pop()) |k| { + // save state (if recording) + if (event_file) |value| { + _ = try value.writeAll(std.mem.asBytes(&frame)); + // TODO: + // writing full k struct is very wasteful, there is a lot of non-essential + // data (for example each release event writes 32 bytes of empty string), + // do not worry about that now, optimize for space later. + _ = try value.writeAll(std.mem.asBytes(&k)); + } + app_state.updateKeyStates(@intCast(k.keycode), k.pressed); const symbol = backend.keysymToString(k.keysym); @@ -833,6 +918,7 @@ pub fn main() !void { } rl.endDrawing(); tracy.frameMark(); + frame += 1; if (renderer) |*r| { const rendering = tracy.traceNamed(@src(), "render"); @@ -852,10 +938,6 @@ pub fn main() !void { ); try r.write(pixels.?); - - if (!backend.is_running) { - exit_window = true; - } } } diff --git a/src/win32.zig b/src/win32.zig index 0265e5c..936d9a1 100644 --- a/src/win32.zig +++ b/src/win32.zig @@ -73,12 +73,11 @@ fn lowLevelKeyboardProc(nCode: c.INT, wParam: c.WPARAM, lParam: c.LPARAM) callco return c.CallNextHookEx(hook.?, nCode, wParam, lParam); } -pub fn listener(app_state: *AppState, window_handle: *anyopaque, record_file: ?[]const u8) !void { +pub fn listener(app_state: *AppState, window_handle: *anyopaque) !void { defer is_running = false; // stopping not implemented yet is_running = true; _ = window_handle; - _ = record_file; app_state_l = app_state; layout = c.GetKeyboardLayout(0); @@ -97,15 +96,6 @@ pub fn keysymToString(keysym: c_ulong) [*c]const u8 { return kbd_en_vscname[@as(usize, @intCast(keysym))].ptr; } -// uses events stored in file to reproduce them -// assumes that only expected event types are recorded -pub fn producer(app_state: *AppState, window_handle: *anyopaque, replay_file: []const u8, loop: bool) !void { - _ = app_state; - _ = window_handle; - _ = replay_file; - _ = loop; -} - // must match names used by X11: const kbd_en_vscname = [_][]const u8 { // zig fmt: off diff --git a/src/x11.zig b/src/x11.zig index 4c781b6..c2ee9a1 100644 --- a/src/x11.zig +++ b/src/x11.zig @@ -105,7 +105,7 @@ fn selectEvents(display: ?*x11.Display, win: x11.Window) void { _ = x11.XSync(display.?, 0); } -pub fn listener(app_state: *AppState, window_handle: *anyopaque, record_file: ?[]const u8) !void { +pub fn listener(app_state: *AppState, window_handle: *anyopaque) !void { defer { std.debug.print("defer x11Listener\n", .{}); is_running = false; @@ -124,16 +124,6 @@ pub fn listener(app_state: *AppState, window_handle: *anyopaque, record_file: ?[ var err: c_int = 0; var xi_opcode: i32 = 0; - // TODO: use buffered writer, to do that we must gracefully handle this thread exit, - // otherwise there is no good place to ensure writer flush - // TODO: support full file path - var event_file: ?fs.File = null; - if (record_file) |filename| { - const cwd = fs.cwd(); - event_file = try cwd.createFile(filename, .{}); - } - defer event_file.?.close(); - if (x11.XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &err) == 0) { std.debug.print("X Input extension not available.\n", .{}); return error.X11InitializationFailed; @@ -169,11 +159,6 @@ pub fn listener(app_state: *AppState, window_handle: *anyopaque, record_file: ?[ // (defined in linux/input-event-codes.h system header): const keycode: u8 = @intCast(device_event.detail - 8); - if (event_file) |file| { - const device_event_data: [*]u8 = @ptrCast(device_event); - _ = try file.writeAll(device_event_data[0..@sizeOf(x11.XIDeviceEvent)]); - } - var key: KeyData = std.mem.zeroInit(KeyData, .{}); key.keycode = keycode; @@ -214,95 +199,95 @@ pub fn keysymToString(keysym: c_ulong) [*c]const u8 { // uses events stored in file to reproduce them // assumes that only expected event types are recorded -pub fn producer(app_state: *AppState, window_handle: *anyopaque, replay_file: []const u8, loop: bool) !void { - defer { - std.debug.print("defer x11Producer\n", .{}); - is_running = false; - } - is_running = true; - - const app_window = glfw.getX11Window(@ptrCast(window_handle)); - std.debug.print("Application x11 window handle: 0x{X}\n", .{app_window}); - - const display: *x11.Display = x11.XOpenDisplay(null) orelse { - std.debug.print("Unable to connect to X server\n", .{}); - return error.X11InitializationFailed; - }; - - var input_ctx = try X11InputContext.init(display, app_window); - defer input_ctx.deinit(); - - var run_loop = true; - std.debug.print("Replay events from file\n", .{}); - - // TODO: support full path of a file - const file = try fs.cwd().openFile(replay_file, .{}); - defer file.close(); - var buf_reader = std.io.bufferedReader(file.reader()); - const reader = buf_reader.reader(); - - out: while (run_loop) { - // Simulate (approximately) timings of recorded events. - // This ignores effect of added delay due to the loop. - var events_count: usize = 0; - var timestamp: x11.Time = 0; // timestamp in x11 events is in milliseconds - var previous_timestamp: x11.Time = 0; - - while (reader.readStruct(x11.XIDeviceEvent)) |device_event| { - if (!is_running) { - break :out; - } - timestamp = device_event.time; - const time_to_wait = timestamp - previous_timestamp; - // first would be large because it is in reference to x11 server start, - // delay only on 1..n event - if (events_count != 0 and time_to_wait != 0) { - std.time.sleep(time_to_wait * std.time.ns_per_ms); - } - - // do stuff with event-from-file - - app_state.updateKeyStates( - @intCast(device_event.detail - 8), - device_event.evtype == x11.XI_KeyPress, - ); - - if (device_event.evtype == x11.XI_KeyPress) { - var key: KeyData = std.mem.zeroInit(KeyData, .{}); - _ = input_ctx.lookupString(&device_event, &key); - - if (key.string[0] != 0) { - app_state.last_char_timestamp = std.time.timestamp(); - } - - while (!app_state.keys.push(key)) : ({ - // this is unlikely scenario - normal typing would not be fast enough - std.debug.print("Consumer outpaced, try again\n", .{}); - std.time.sleep(10 * std.time.ns_per_ms); - }) {} - std.debug.print("Produced (fake): '{any}'\n", .{key}); - } - - // continue with next events - previous_timestamp = timestamp; - events_count += 1; - } else |err| switch (err) { - error.EndOfStream => { - std.debug.print("End of file\n", .{}); - if (loop) { - try file.seekTo(0); - for (app_state.key_states, 0..) |_, i| { - var s = &app_state.key_states[i]; - s.pressed = false; - } - } else { - run_loop = false; - } - }, - else => return err, - } - } -} +//pub fn producer(app_state: *AppState, window_handle: *anyopaque, replay_file: []const u8, loop: bool) !void { +// defer { +// std.debug.print("defer x11Producer\n", .{}); +// is_running = false; +// } +// is_running = true; +// +// const app_window = glfw.getX11Window(@ptrCast(window_handle)); +// std.debug.print("Application x11 window handle: 0x{X}\n", .{app_window}); +// +// const display: *x11.Display = x11.XOpenDisplay(null) orelse { +// std.debug.print("Unable to connect to X server\n", .{}); +// return error.X11InitializationFailed; +// }; +// +// var input_ctx = try X11InputContext.init(display, app_window); +// defer input_ctx.deinit(); +// +// var run_loop = true; +// std.debug.print("Replay events from file\n", .{}); +// +// // TODO: support full path of a file +// const file = try fs.cwd().openFile(replay_file, .{}); +// defer file.close(); +// var buf_reader = std.io.bufferedReader(file.reader()); +// const reader = buf_reader.reader(); +// +// out: while (run_loop) { +// // Simulate (approximately) timings of recorded events. +// // This ignores effect of added delay due to the loop. +// var events_count: usize = 0; +// var timestamp: x11.Time = 0; // timestamp in x11 events is in milliseconds +// var previous_timestamp: x11.Time = 0; +// +// while (reader.readStruct(x11.XIDeviceEvent)) |device_event| { +// if (!is_running) { +// break :out; +// } +// timestamp = device_event.time; +// const time_to_wait = timestamp - previous_timestamp; +// // first would be large because it is in reference to x11 server start, +// // delay only on 1..n event +// if (events_count != 0 and time_to_wait != 0) { +// std.time.sleep(time_to_wait * std.time.ns_per_ms); +// } +// +// // do stuff with event-from-file +// +// app_state.updateKeyStates( +// @intCast(device_event.detail - 8), +// device_event.evtype == x11.XI_KeyPress, +// ); +// +// if (device_event.evtype == x11.XI_KeyPress) { +// var key: KeyData = std.mem.zeroInit(KeyData, .{}); +// _ = input_ctx.lookupString(&device_event, &key); +// +// if (key.string[0] != 0) { +// app_state.last_char_timestamp = std.time.timestamp(); +// } +// +// while (!app_state.keys.push(key)) : ({ +// // this is unlikely scenario - normal typing would not be fast enough +// std.debug.print("Consumer outpaced, try again\n", .{}); +// std.time.sleep(10 * std.time.ns_per_ms); +// }) {} +// std.debug.print("Produced (fake): '{any}'\n", .{key}); +// } +// +// // continue with next events +// previous_timestamp = timestamp; +// events_count += 1; +// } else |err| switch (err) { +// error.EndOfStream => { +// std.debug.print("End of file\n", .{}); +// if (loop) { +// try file.seekTo(0); +// for (app_state.key_states, 0..) |_, i| { +// var s = &app_state.key_states[i]; +// s.pressed = false; +// } +// } else { +// run_loop = false; +// } +// }, +// else => return err, +// } +// } +//} pub fn get_mouse_position() !struct { x: usize, y: usize } { // Open a connection to the X server