diff --git a/README.md b/README.md index 0d49262..18c0d24 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,8 @@ ```shell zig build run -``` \ No newline at end of file +``` + +## TODO + +* Use downsampling for faster rendering of samples. When viewing many samples use dowsampled versions of data for rendering. Because you either way, you won't be able to see the detail. \ No newline at end of file diff --git a/src/app.zig b/src/app.zig index 74b03ef..541ab60 100644 --- a/src/app.zig +++ b/src/app.zig @@ -15,6 +15,8 @@ const clamp = std.math.clamp; const App = @This(); +const max_channels = 64; + const Channel = struct { view_cache: Graph.Cache = .{}, view_rect: Graph.ViewOptions, @@ -31,7 +33,7 @@ const Channel = struct { allocator: std.mem.Allocator, ui: UI, -channels: std.BoundedArray(Channel, 64) = .{}, +channels: std.BoundedArray(Channel, max_channels) = .{}, ni_daq: NIDaq, shown_window: enum { @@ -39,6 +41,11 @@ shown_window: enum { add_from_device } = .channels, +device_filter: NIDaq.BoundedDeviceName = .{}, +show_voltage_analog_inputs: bool = true, +show_voltage_analog_outputs: bool = true, +selected_channels: std.BoundedArray([:0]u8, max_channels) = .{}, + pub fn init(allocator: std.mem.Allocator) !App { return App{ .allocator = allocator, @@ -51,7 +58,7 @@ pub fn init(allocator: std.mem.Allocator) !App { .max_counter_inputs = 8, .max_analog_input_voltage_ranges = 4, .max_analog_output_voltage_ranges = 4 - }), + }) }; } @@ -64,6 +71,11 @@ pub fn deinit(self: *App) void { channel.view_cache.deinit(); } + for (self.selected_channels.constSlice()) |channel| { + self.allocator.free(channel); + } + self.selected_channels.len = 0; + self.ui.deinit(); } @@ -280,9 +292,182 @@ fn showChannelsWindow(self: *App) void { } } +fn findChannelIndexByName(haystack: []const [:0]const u8, needle: [:0]const u8) ?usize { + for (0.., haystack) |i, item| { + if (std.mem.eql(u8, item, needle)) { + return i; + } + } + return null; +} + fn showAddFromDeviceWindow(self: *App) !void { - const names = try self.ni_daq.listDeviceNames(); - _ = names; + const window = self.ui.newBoxFromString("Device window"); + window.size.x = UI.Size.percent(1, 0); + window.size.y = UI.Size.percent(1, 0); + window.layout_axis = .X; + self.ui.pushParent(window); + defer self.ui.popParent(); + + { + const filters_box = self.ui.newBoxFromString("Filters box"); + filters_box.size.x = UI.Size.percent(0.5, 1); + filters_box.size.y = UI.Size.percent(1, 0); + filters_box.layout_axis = .Y; + self.ui.pushParent(filters_box); + defer self.ui.popParent(); + + for (try self.ni_daq.listDeviceNames()) |device| { + const device_box = self.ui.newBoxFromString(device); + device_box.flags.insert(.clickable); + device_box.size.x = UI.Size.text(2, 1); + device_box.size.y = UI.Size.text(2, 1); + device_box.setText(.text, device); + + const signal = self.ui.signalFromBox(device_box); + if (signal.clicked()) { + self.device_filter = try NIDaq.BoundedDeviceName.fromSlice(device); + } + } + + { + const toggle_inputs_box = self.ui.newBoxFromString("Toggle inputs"); + toggle_inputs_box.flags.insert(.clickable); + toggle_inputs_box.size.x = UI.Size.text(2, 1); + toggle_inputs_box.size.y = UI.Size.text(2, 1); + toggle_inputs_box.setText(.text, if (self.show_voltage_analog_inputs) "Hide inputs" else "Show inputs"); + const signal = self.ui.signalFromBox(toggle_inputs_box); + if (signal.clicked()) { + self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs; + } + } + + { + const toggle_outputs_box = self.ui.newBoxFromString("Toggle outputs"); + toggle_outputs_box.flags.insert(.clickable); + toggle_outputs_box.size.x = UI.Size.text(2, 1); + toggle_outputs_box.size.y = UI.Size.text(2, 1); + toggle_outputs_box.setText(.text, if (self.show_voltage_analog_outputs) "Hide outputs" else "Show outputs"); + const signal = self.ui.signalFromBox(toggle_outputs_box); + if (signal.clicked()) { + self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs; + } + } + + { + const add_button = self.ui.newBoxFromString("Add"); + add_button.flags.insert(.clickable); + add_button.size.x = UI.Size.text(2, 1); + add_button.size.y = UI.Size.text(2, 1); + add_button.setText(.text, "Add selected"); + const signal = self.ui.signalFromBox(add_button); + if (signal.clicked()) { + const selected_devices = self.selected_channels.constSlice(); + std.debug.print("{s}\n", .{selected_devices}); + + for (self.selected_channels.constSlice()) |channel| { + self.allocator.free(channel); + } + self.selected_channels.len = 0; + + self.shown_window = .channels; + } + } + } + + { + const channels_box = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels list")); + defer self.ui.popScrollbar(); + channels_box.layout_axis = .Y; + channels_box.size.x = UI.Size.percent(1, 0); + + var devices: []const [:0]const u8 = &.{}; + if (self.device_filter.len > 0) { + devices = &.{ + self.device_filter.buffer[0..self.device_filter.len :0] + }; + } else { + devices = try self.ni_daq.listDeviceNames(); + } + + for (devices) |device| { + var ai_voltage_physical_channels: []const [:0]const u8 = &.{}; + if (self.show_voltage_analog_inputs) { + if (try self.ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) { + ai_voltage_physical_channels = try self.ni_daq.listDeviceAIPhysicalChannels(device); + } + } + + var ao_physical_channels: []const [:0]const u8 = &.{}; + if (self.show_voltage_analog_outputs) { + if (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) { + ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device); + } + } + + inline for (.{ ai_voltage_physical_channels, ao_physical_channels }) |channels| { + for (channels) |channel| { + const selected_channels_slice = self.selected_channels.constSlice(); + + const channel_box = self.ui.newBoxFromString(channel); + channel_box.flags.insert(.clickable); + channel_box.size.x = UI.Size.text(1, 1); + channel_box.size.y = UI.Size.text(0.5, 1); + channel_box.setText(.text, channel); + + if (findChannelIndexByName(selected_channels_slice, channel) != null) { + channel_box.background = srcery.xgray3; + } + + const signal = self.ui.signalFromBox(channel_box); + if (signal.clicked()) { + if (findChannelIndexByName(selected_channels_slice, channel)) |index| { + self.allocator.free(self.selected_channels.swapRemove(index)); + } else { + self.selected_channels.appendAssumeCapacity(try self.allocator.dupeZ(u8, channel)); + } + } + + } + } + } + } + + // if (self.selected_device.len > 0) { + // const device: [:0]u8 = self.selected_device.buffer[0..self.selected_device.len :0]; + + // var ai_voltage_physical_channels: []const [:0]const u8 = &.{}; + // if (try self.ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) { + // ai_voltage_physical_channels = try self.ni_daq.listDeviceAIPhysicalChannels(device); + // } + + // var ao_physical_channels: []const [:0]const u8 = &.{}; + // if (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) { + // ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device); + // } + + // const channels_box = self.ui.newBoxFromString("Channel names"); + // channels_box.size.x = UI.Size.percent(0.5, 0); + // channels_box.size.y = UI.Size.percent(1, 0); + // channels_box.layout_axis = .Y; + // self.ui.pushParent(channels_box); + // defer self.ui.popParent(); + + // for (ai_voltage_physical_channels) |channel_name| { + // const channel_box = self.ui.newBoxFromString(channel_name); + // channel_box.flags.insert(.clickable); + // channel_box.size.x = UI.Size.text(1, 0); + // channel_box.size.y = UI.Size.text(1, 0); + // channel_box.setText(.text, channel_name); + + // const signal = self.ui.signalFromBox(channel_box); + // if (signal.clicked()) { + // self.selected_device = try NIDaq.BoundedDeviceName.fromSlice(device); + // } + // } + // } + + } fn showToolbar(self: *App) void { @@ -305,7 +490,7 @@ fn showToolbar(self: *App) void { .x = UI.Size.text(2, 1), .y = UI.Size.percent(1, 1) }; - box.setText("Add from file", .text); + box.setText(.text, "Add from file",); const signal = self.ui.signalFromBox(box); if (signal.clicked()) { @@ -329,7 +514,7 @@ fn showToolbar(self: *App) void { .x = UI.Size.text(2, 1), .y = UI.Size.percent(1, 1) }; - box.setText("Add from device", .text); + box.setText(.text, "Add from device"); const signal = self.ui.signalFromBox(box); if (signal.clicked()) { diff --git a/src/ni-daq.zig b/src/ni-daq.zig index 5947513..217cbec 100644 --- a/src/ni-daq.zig +++ b/src/ni-daq.zig @@ -11,7 +11,16 @@ const log = std.log.scoped(.ni_daq); const max_device_name_size = 255; const max_task_name_size = 255; -pub const BoundedDeviceName = std.BoundedArray(u8, max_device_name_size); +const max_channel_name_size = count: { + var count: u32 = 0; + count += max_device_name_size; + count += 1; // '/' + count += 2; // 'ai' or 'ao' or 'co' or 'ci' + count += 3; // '0' -> '999', I can't imagine this counter being over 1000. What device has over 1000 channels???? + break :count count; +}; + +pub const BoundedDeviceName = std.BoundedArray(u8, max_device_name_size + 1); // +1 for null byte const StringArrayListUnmanaged = std.ArrayListUnmanaged([:0]const u8); const NIDaq = @This(); @@ -276,17 +285,7 @@ const DeviceBuffers = struct { array_list: StringArrayListUnmanaged, fn init(allocator: std.mem.Allocator, capacity: u32) !ChannelNames { - const max_channel_name_size = count: { - var count: u32 = 0; - count += max_device_name_size; - count += 1; // '/' - count += 2; // 'ai' or 'ao' or 'co' or 'ci' - count += std.math.log10_int(capacity) + 1; - - break :count count; - }; - - const buffer_size = capacity * (max_channel_name_size + 2); + const buffer_size = capacity * (max_channel_name_size + 2); // +2 for ', ' separator const buffer = try allocator.alloc(u8, buffer_size); errdefer allocator.free(buffer); @@ -434,13 +433,7 @@ pub fn logDAQmxError(error_code: i32) void { var msg: [512:0]u8 = .{ 0 } ** 512; if (c.DAQmxGetErrorString(error_code, &msg, msg.len) == 0) { - if (error_code < 0) { - log.err("DAQmx ({}): {s}", .{error_code, msg}); - } else if (!std.mem.startsWith(u8, &msg, "Error code could not be found.")) { - // Ignore positive error codes if it could not be found. - // This commonly happens when trying to preallocate bytes for buffer and it returns a positive number. - log.warn("DAQmx ({}): {s}", .{error_code, msg}); - } + log.err("DAQmx ({}): {s}", .{error_code, msg}); } else { log.err("DAQmx ({}): Unknown (Buffer too small for message)", .{error_code}); } @@ -454,6 +447,14 @@ pub fn checkDAQmxError(error_code: i32, err: anyerror) !void { } } +pub fn checkDAQmxErrorIgnoreWarnings(error_code: i32, err: anyerror) !void { + if (error_code > 0) { + return; + } + + try checkDAQmxError(error_code, err); +} + fn splitCommaDelimitedList(array_list: *std.ArrayListUnmanaged([:0]const u8), buffer: []u8) !void { const count = std.mem.count(u8, buffer, ",") + 1; if (count > array_list.capacity) { @@ -504,6 +505,7 @@ pub fn listDeviceNames(self: *NIDaq) ![]const [:0]const u8 { self.clearAllDeviceBuffers(); const required_size = c.DAQmxGetSysDevNames(null, 0); + try checkDAQmxErrorIgnoreWarnings(required_size, error.DAQmxGetSysDevNames); if (required_size == 0) { return self.device_names.items; } @@ -560,7 +562,7 @@ fn listDevicePhysicalChannels( array_list.clearRetainingCapacity(); const required_size = getPhysicalChannels(device, null, 0); - try checkDAQmxError(required_size, error.GetPhysicalChannels); + try checkDAQmxErrorIgnoreWarnings(required_size, error.GetPhysicalChannels); if (required_size == 0) { return array_list.items; } @@ -710,7 +712,7 @@ fn listDeviceVoltageRanges( voltage_ranges.clearRetainingCapacity(); const count = getVoltageRanges(device, null, 0); - try checkDAQmxError(count, error.GetVoltageRanges); + try checkDAQmxErrorIgnoreWarnings(count, error.GetVoltageRanges); if (count == 0) { return voltage_ranges.items; } @@ -771,7 +773,7 @@ pub fn listDeviceAIMeasurementTypes(self: NIDaq, device: [:0]const u8) !AIMeasur _ = self; const count = c.DAQmxGetDevAISupportedMeasTypes(device, null, 0); - try checkDAQmxError(count, error.DAQmxGetDevAISupportedMeasTypes); + try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAISupportedMeasTypes); assert(count <= result.buffer.len); try checkDAQmxError( @@ -794,7 +796,7 @@ pub fn listDeviceAOOutputTypes(self: NIDaq, device: [:0]const u8) !AOOutputTypeL _ = self; const count = c.DAQmxGetDevAOSupportedOutputTypes(device, null, 0); - try checkDAQmxError(count, error.DAQmxGetDevAOSupportedOutputTypes); + try checkDAQmxErrorIgnoreWarnings(count, error.DAQmxGetDevAOSupportedOutputTypes); assert(count <= result.buffer.len); try checkDAQmxError( diff --git a/src/ui.zig b/src/ui.zig index e87bee9..e389189 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -287,13 +287,20 @@ pub const Box = struct { }; } - pub fn setText(self: *Box, text: []const u8, font: Assets.FontId) void { + pub fn setText(self: *Box, font: Assets.FontId, text: []const u8) void { self.text = .{ .content = self.allocator.dupe(u8, text) catch return, .font = font }; } + pub fn setAllocText(self: *Box, font: Assets.FontId, comptime fmt: []const u8, args: anytype) void { + self.text = .{ + .content = std.fmt.allocPrint(self.allocator, fmt, args) catch return, + .font = font + }; + } + pub fn setFixedX(self: *Box, x: f32) void { self.flags.insert(.fixed_x); self.fixed_rect.x = x; @@ -452,6 +459,19 @@ pub fn begin(self: *UI) void { ); } + { + var i: usize = 0; + while (i < self.boxes.len) { + const box = &self.boxes.buffer[i]; + if (box.last_used_frame != self.frame_index) { + _ = self.boxes.swapRemove(i); + } else { + i += 1; + } + + } + } + self.frame_index += 1; _ = self.frameArena().reset(.retain_capacity); self.parent_index_stack.len = 0; @@ -489,19 +509,6 @@ pub fn end(self: *UI) void { self.popParent(); assert(self.parent_index_stack.len == 0); - { - var i: usize = 0; - while (i < self.boxes.len) { - const box = &self.boxes.buffer[i]; - if (box.last_used_frame != self.frame_index) { - _ = self.boxes.swapRemove(i); - continue; - } - - i += 1; - } - } - { var active_box_flags = self.getActiveBoxFlags(); var hover_box_flags: Box.Flags = .{}; @@ -921,12 +928,13 @@ pub fn newBox(self: *UI, key: Key) *Box { var box: *Box = undefined; var box_index: BoxIndex = undefined; var persistent: Box.Persistent = .{}; - if (self.findBoxByKey(key)) |found_box| { + if (self.findBoxIndexByKey(key)) |found_box_index| { + const found_box = &self.boxes.buffer[found_box_index]; assert(found_box.last_used_frame < self.frame_index); persistent = found_box.persistent; box = found_box; - box_index = found_box.index; + box_index = found_box_index; } else { box = self.boxes.addOneAssumeCapacity(); box_index = self.boxes.len - 1; @@ -941,8 +949,9 @@ pub fn newBox(self: *UI, key: Key) *Box { .index = box_index }; - if (self.getParent()) |parent| { - box.parent_index = parent.index; + if (self.getParentIndex()) |parent_index| { + const parent = &self.boxes.buffer[parent_index]; + box.parent_index = parent_index; if (parent.last_child_index) |last_child_index| { const last_child = &self.boxes.buffer[last_child_index]; @@ -958,15 +967,22 @@ pub fn newBox(self: *UI, key: Key) *Box { return box; } -fn findBoxByKey(self: *UI, key: Key) ?*Box { - for (self.boxes.slice()) |*box| { +fn findBoxIndexByKey(self: *UI, key: Key) ?BoxIndex { + for (0.., self.boxes.slice()) |index, *box| { if (box.key.eql(key)) { - return box; + return @intCast(index); } } return null; } +fn findBoxByKey(self: *UI, key: Key) ?*Box { + if (self.findBoxIndexByKey(key)) |box_index| { + return &self.boxes.buffer[box_index]; + } + return null; +} + pub fn signalFromBox(self: *UI, box: *Box) Signal { var result = Signal{}; @@ -1058,11 +1074,18 @@ fn isBoxActive(self: *UI, key: Key) bool { return false; } -pub fn getParent(self: *UI) ?*Box { +fn getParentIndex(self: *UI) ?BoxIndex { const parent_stack: []BoxIndex = self.parent_index_stack.slice(); if (parent_stack.len > 0) { - const parent_index = parent_stack[parent_stack.len - 1]; + return parent_stack[parent_stack.len - 1]; + } else { + return null; + } +} + +pub fn getParent(self: *UI) ?*Box { + if (self.getParentIndex()) |parent_index| { return &self.boxes.buffer[parent_index]; } else { return null;