refactor sample list to separate list in project

This commit is contained in:
Rokas Puzonas 2025-04-30 00:01:07 +03:00
parent e5bc57d7b5
commit af8cf21f53
8 changed files with 244 additions and 154 deletions

View File

@ -166,6 +166,23 @@ fn GenerationalArray(Item: type) type {
}; };
} }
pub const SampleList = struct {
samples: std.ArrayListUnmanaged(f64) = .{},
graph_min_max_cache: Graph.MinMaxCache = .{},
min_sample: ?f64 = null,
max_sample: ?f64 = null,
pub fn clear(self: *SampleList, allocator: Allocator) void {
self.deinit(allocator);
self.* = .{};
}
pub fn deinit(self: *SampleList, allocator: Allocator) void {
self.samples.clearAndFree(allocator);
self.graph_min_max_cache.deinit(allocator);
}
};
pub const Channel = struct { pub const Channel = struct {
const Name = std.BoundedArray(u8, NIDaq.max_channel_name_size + 1); const Name = std.BoundedArray(u8, NIDaq.max_channel_name_size + 1);
const Device = std.BoundedArray(u8, NIDaq.max_device_name_size + 1); const Device = std.BoundedArray(u8, NIDaq.max_device_name_size + 1);
@ -176,15 +193,12 @@ pub const Channel = struct {
// Runtime // Runtime
device: Device = .{}, device: Device = .{},
collected_samples_id: Id,
allowed_sample_values: ?RangeF64 = null, allowed_sample_values: ?RangeF64 = null,
allowed_sample_rates: ?RangeF64 = null, allowed_sample_rates: ?RangeF64 = null,
// TODO: Use a linked list, so the whole list wouldn't need to be reallocated when appending collected samples
collected_samples: std.ArrayListUnmanaged(f64) = .{},
write_pattern: std.ArrayListUnmanaged(f64) = .{}, write_pattern: std.ArrayListUnmanaged(f64) = .{},
output_task: ?NIDaq.Task = null, output_task: ?NIDaq.Task = null,
graph_min_max_cache: Graph.MinMaxCache = .{},
last_sample_save_at: ?i128 = null, last_sample_save_at: ?i128 = null,
processed_samples_up_to: usize = 0,
pub fn deinit(self: *Channel, allocator: Allocator) void { pub fn deinit(self: *Channel, allocator: Allocator) void {
self.clear(allocator); self.clear(allocator);
@ -200,14 +214,11 @@ pub const Channel = struct {
self.allowed_sample_values = null; self.allowed_sample_values = null;
self.last_sample_save_at = null; self.last_sample_save_at = null;
self.collected_samples.clearAndFree(allocator);
self.write_pattern.clearAndFree(allocator); self.write_pattern.clearAndFree(allocator);
if (self.output_task) |task| { if (self.output_task) |task| {
task.clear(); task.clear();
self.output_task = null; self.output_task = null;
} }
self.graph_min_max_cache.deinit(allocator);
} }
pub fn generateSine( pub fn generateSine(
@ -247,10 +258,6 @@ pub const Channel = struct {
pub fn invalidateSavedSamples(self: *Channel) void { pub fn invalidateSavedSamples(self: *Channel) void {
self.last_sample_save_at = null; self.last_sample_save_at = null;
} }
fn hasNewCollectedSamples(self: *Channel) bool {
return self.processed_samples_up_to != self.collected_samples.items.len;
}
}; };
pub const File = struct { pub const File = struct {
@ -258,24 +265,16 @@ pub const File = struct {
path: []u8, path: []u8,
// Runtime // Runtime
samples: ?[]f64 = null, samples_id: Id,
graph_min_max_cache: Graph.MinMaxCache = .{},
min_sample: f64 = 0,
max_sample: f64 = 0,
pub fn deinit(self: *File, allocator: Allocator) void { pub fn deinit(self: *File, allocator: Allocator) void {
self.clear(allocator); self.clear(allocator);
self.graph_min_max_cache.deinit(allocator);
allocator.free(self.path); allocator.free(self.path);
} }
pub fn clear(self: *File, allocator: Allocator) void { pub fn clear(self: *File, allocator: Allocator) void {
if (self.samples) |samples| { _ = self;
allocator.free(samples); _ = allocator;
self.samples = null;
}
self.min_sample = 0;
self.max_sample = 0;
} }
}; };
@ -373,12 +372,14 @@ pub const View = struct {
graph_opts: Graph.ViewOptions = .{}, graph_opts: Graph.ViewOptions = .{},
sync_controls: bool = false, sync_controls: bool = false,
marked_ranges: std.BoundedArray(MarkedRange, 32) = .{}, marked_ranges: std.BoundedArray(MarkedRange, 32) = .{},
sliding_window: ?f64 = null,
// Runtime // Runtime
graph_cache: Graph.RenderCache = .{}, graph_cache: Graph.RenderCache = .{},
available_x_range: RangeF64 = RangeF64.init(0, 0), available_x_range: RangeF64 = RangeF64.init(0, 0),
available_y_range: RangeF64 = RangeF64.init(0, 0), available_y_range: RangeF64 = RangeF64.init(0, 0),
unit: ?NIDaq.Unit = .Voltage, unit: ?NIDaq.Unit = .Voltage,
transformed_samples: ?[]f64 = null,
pub fn clear(self: *View) void { pub fn clear(self: *View) void {
self.graph_cache.clear(); self.graph_cache.clear();
@ -431,6 +432,7 @@ pub const Project = struct {
// TODO: How this to computer local settings, like appdata. Because this option shouldn't be project specific. // TODO: How this to computer local settings, like appdata. Because this option shouldn't be project specific.
show_rulers: bool = true, show_rulers: bool = true,
sample_lists: GenerationalArray(SampleList) = .{},
channels: GenerationalArray(Channel) = .{}, channels: GenerationalArray(Channel) = .{},
files: GenerationalArray(File) = .{}, files: GenerationalArray(File) = .{},
views: GenerationalArray(View) = .{}, views: GenerationalArray(View) = .{},
@ -467,23 +469,88 @@ pub const Project = struct {
return result_range; return result_range;
} }
pub fn getViewSampleList(self: *Project, view_id: Id) Id {
const view = self.views.get(view_id).?;
switch (view.reference) {
.channel => |channel_id| {
const channel = self.channels.get(channel_id).?;
return channel.collected_samples_id;
},
.file => |file_id| {
const file = self.files.get(file_id).?;
return file.samples_id;
}
}
}
pub fn getViewSamples(self: *Project, view_id: Id) []const f64 { pub fn getViewSamples(self: *Project, view_id: Id) []const f64 {
const empty = &[0]f64{}; const samples_list_id = self.getViewSampleList(view_id);
const samples_list = self.sample_lists.get(samples_list_id).?;
var result: []const f64 = empty; return samples_list.samples.items;
}
if (self.views.get(view_id)) |view| { pub fn appendSamples(self: *Project, allocator: Allocator, sample_list_id: Id, samples: []const f64) !void {
switch (view.reference) { if (samples.len == 0) return;
.channel => |channel_id| if (self.channels.get(channel_id)) |channel| {
result = channel.collected_samples.items; const sample_list = self.sample_lists.get(sample_list_id).?;
},
.file => |file_id| if (self.files.get(file_id)) |file| { const affected_range = RangeF64.init(
result = file.samples orelse empty; @floatFromInt(sample_list.samples.items.len),
@floatFromInt(sample_list.samples.items.len + samples.len)
);
try sample_list.samples.appendSlice(allocator, samples);
var min_sample = sample_list.min_sample orelse samples[0];
var max_sample = sample_list.max_sample orelse samples[0];
for (samples) |sample| {
min_sample = @min(min_sample, sample);
max_sample = @max(max_sample, sample);
}
sample_list.min_sample = min_sample;
sample_list.max_sample = max_sample;
try sample_list.graph_min_max_cache.updateLast(allocator, sample_list.samples.items);
self.refreshMarkedRanges(sample_list_id, affected_range);
}
pub fn clearSamples(self: *Project, allocator: Allocator, sample_list_id: Id) void {
const sample_list = self.sample_lists.get(sample_list_id).?;
const affected_range = RangeF64.init(
0,
@floatFromInt(sample_list.samples.items.len)
);
sample_list.clear(allocator);
self.refreshMarkedRanges(sample_list_id, affected_range);
}
fn refreshMarkedRanges(self: *Project, sample_list_id: Id, affected_range: RangeF64) void {
const sample_list = self.sample_lists.get(sample_list_id).?;
const total_samples = sample_list.samples.items;
var view_iter = self.views.idIterator();
while (view_iter.next()) |view_id| {
const view_sample_list_id = self.getViewSampleList(view_id);
if (!view_sample_list_id.eql(sample_list_id)) {
continue;
}
const view = self.views.get(view_id).?;
const marked_ranges: []View.MarkedRange = view.marked_ranges.slice();
for (marked_ranges) |*marked_range| {
if (marked_range.axis != .X) continue;
if (affected_range.intersectPositive(marked_range.range).isPositive()) {
marked_range.refresh(total_samples);
} }
} }
} }
return result;
} }
pub fn deinit(self: *Project, allocator: Allocator) void { pub fn deinit(self: *Project, allocator: Allocator) void {
@ -501,6 +568,12 @@ pub const Project = struct {
self.views.clear(); self.views.clear();
var sample_lists_iter = self.sample_lists.iterator();
while (sample_lists_iter.next()) |sample_list| {
sample_list.clear(allocator);
}
self.sample_lists.clear();
if (self.save_location) |str| { if (self.save_location) |str| {
allocator.free(str); allocator.free(str);
self.save_location = null; self.save_location = null;
@ -548,10 +621,14 @@ pub const Project = struct {
} }
errdefer if (saved_collected_samples) |str| allocator.free(str); errdefer if (saved_collected_samples) |str| allocator.free(str);
const sample_list_id = try self.sample_lists.insert(.{});
errdefer self.sample_lists.remove(sample_list_id);
const channel = self.channels.get(id).?; const channel = self.channels.get(id).?;
channel.* = Channel{ channel.* = Channel{
.name = try utils.initBoundedStringZ(Channel.Name, channel_name), .name = try utils.initBoundedStringZ(Channel.Name, channel_name),
.saved_collected_samples = saved_collected_samples .saved_collected_samples = saved_collected_samples,
.collected_samples_id = sample_list_id
}; };
} }
} }
@ -566,9 +643,13 @@ pub const Project = struct {
const path = try readString(reader, allocator); const path = try readString(reader, allocator);
errdefer allocator.free(path); errdefer allocator.free(path);
const sample_list_id = try self.sample_lists.insert(.{});
errdefer self.sample_lists.remove(sample_list_id);
const file = self.files.get(id).?; const file = self.files.get(id).?;
file.* = File{ file.* = File{
.path = path .path = path,
.samples_id = sample_list_id
}; };
} }
} }
@ -654,7 +735,8 @@ pub const Project = struct {
const samples_file = try dir.createFile(path, .{}); const samples_file = try dir.createFile(path, .{});
defer samples_file.close(); defer samples_file.close();
try writeFileF64(samples_file, channel.collected_samples.items); const samples = self.sample_lists.get(channel.collected_samples_id).?.samples.items;
try writeFileF64(samples_file, samples);
} }
const stat = try dir.statFile(path); const stat = try dir.statFile(path);
@ -848,7 +930,7 @@ pub fn init(self: *App, allocator: Allocator) !void {
self.ni_daq = try NIDaq.init(allocator, &self.ni_daq_api.?, .{ self.ni_daq = try NIDaq.init(allocator, &self.ni_daq_api.?, .{
.max_devices = 4, .max_devices = 4,
.max_analog_inputs = 32, .max_analog_inputs = 32,
.max_analog_outputs = 8, .max_analog_outputs = 16,
.max_counter_outputs = 8, .max_counter_outputs = 8,
.max_counter_inputs = 8, .max_counter_inputs = 8,
.max_analog_input_voltage_ranges = 4, .max_analog_input_voltage_ranges = 4,
@ -1012,42 +1094,6 @@ pub fn tick(self: *App) !void {
self.collection_samples_mutex.lock(); self.collection_samples_mutex.lock();
defer self.collection_samples_mutex.unlock(); defer self.collection_samples_mutex.unlock();
{
var view_iter = self.project.views.idIterator();
while (view_iter.next()) |id| {
const view = self.getView(id) orelse continue;
if (view.reference != .channel) continue;
const channel_id = view.reference.channel;
const channel = self.getChannel(channel_id) orelse continue;
if (!channel.hasNewCollectedSamples()) continue;
const samples = channel.collected_samples.items;
const new_samples_range = RangeF64.init(@floatFromInt(channel.processed_samples_up_to), @floatFromInt(samples.len));
const marked_ranges: []View.MarkedRange = view.marked_ranges.slice();
for (marked_ranges) |*marked_range| {
if (marked_range.axis != .X) continue;
if (new_samples_range.intersectPositive(marked_range.range).isPositive()) {
marked_range.refresh(samples);
}
}
}
}
// Update channel min max caches
{
var channel_iter = self.project.channels.iterator();
while (channel_iter.next()) |channel| {
if (channel.hasNewCollectedSamples()) {
channel.invalidateSavedSamples();
try channel.graph_min_max_cache.updateLast(self.allocator, channel.collected_samples.items);
channel.processed_samples_up_to = channel.collected_samples.items.len;
}
}
}
{ {
var view_iter = self.project.views.idIterator(); var view_iter = self.project.views.idIterator();
while (view_iter.next()) |id| { while (view_iter.next()) |id| {
@ -1199,7 +1245,7 @@ fn startCollection(self: *App) !void {
for (channels_in_task.constSlice()) |id| { for (channels_in_task.constSlice()) |id| {
const channel = self.getChannel(id).?; const channel = self.getChannel(id).?;
channel.collected_samples.clearAndFree(self.allocator); self.project.clearSamples(self.allocator, channel.collected_samples_id);
} }
const read_array = try self.allocator.alloc(f64, const read_array = try self.allocator.alloc(f64,
@ -1345,7 +1391,7 @@ pub fn collectionThreadCallback(self: *App) void {
const channel_id = collection_task.channels.get(channel_index); const channel_id = collection_task.channels.get(channel_index);
const channel = self.getChannel(channel_id) orelse continue; const channel = self.getChannel(channel_id) orelse continue;
channel.collected_samples.appendSlice(self.allocator, channel_samples) catch |e| { self.project.appendSamples(self.allocator, channel.collected_samples_id, channel_samples) catch |e| {
log.err("Failed to append samples for channel: {}", .{e}); log.err("Failed to append samples for channel: {}", .{e});
continue; continue;
}; };
@ -1374,9 +1420,13 @@ pub fn addFile(self: *App, path: []const u8) !Id {
const path_dupe = try self.allocator.dupe(u8, path); const path_dupe = try self.allocator.dupe(u8, path);
errdefer self.allocator.free(path_dupe); errdefer self.allocator.free(path_dupe);
const sample_list_id = try self.project.sample_lists.insert(.{});
errdefer self.project.sample_lists.remove(sample_list_id);
const file = self.project.files.get(id).?; const file = self.project.files.get(id).?;
file.* = File{ file.* = File{
.path = path_dupe .path = path_dupe,
.samples_id = sample_list_id
}; };
self.loadFile(id) catch |e| { self.loadFile(id) catch |e| {
@ -1423,7 +1473,10 @@ fn writeFileF64(file: std.fs.File, data: []const f64) !void {
pub fn loadFile(self: *App, id: Id) !void { pub fn loadFile(self: *App, id: Id) !void {
const file = self.getFile(id) orelse return; const file = self.getFile(id) orelse return;
const sample_list = self.project.sample_lists.get(file.samples_id) orelse return;
file.clear(self.allocator); file.clear(self.allocator);
sample_list.clear(self.allocator);
const cwd = std.fs.cwd(); const cwd = std.fs.cwd();
@ -1431,18 +1484,9 @@ pub fn loadFile(self: *App, id: Id) !void {
defer samples_file.close(); defer samples_file.close();
const samples = try readFileF64(self.allocator, samples_file); const samples = try readFileF64(self.allocator, samples_file);
file.samples = samples; defer self.allocator.free(samples);
if (samples.len > 0) { try self.project.appendSamples(self.allocator, file.samples_id, samples);
file.min_sample = samples[0];
file.max_sample = samples[0];
for (samples) |sample| {
file.min_sample = @min(file.min_sample, sample);
file.max_sample = @max(file.max_sample, sample);
}
try file.graph_min_max_cache.updateAll(self.allocator, samples);
}
} }
// ---------------- Channels --------------------------------- // // ---------------- Channels --------------------------------- //
@ -1455,9 +1499,13 @@ pub fn addChannel(self: *App, channel_name: []const u8) !Id {
const id = try self.project.channels.insertUndefined(); const id = try self.project.channels.insertUndefined();
errdefer self.project.channels.remove(id); errdefer self.project.channels.remove(id);
const sample_list_id = try self.project.sample_lists.insert(.{});
errdefer self.project.sample_lists.remove(sample_list_id);
const channel = self.project.channels.get(id).?; const channel = self.project.channels.get(id).?;
channel.* = Channel{ channel.* = Channel{
.name = try utils.initBoundedStringZ(Channel.Name, channel_name), .name = try utils.initBoundedStringZ(Channel.Name, channel_name),
.collected_samples_id = sample_list_id
}; };
if (self.project.save_location) |project_file_path| { if (self.project.save_location) |project_file_path| {
@ -1527,7 +1575,9 @@ fn loadSavedSamples(self: *App, channel_id: Id) !void {
defer samples_file.close(); defer samples_file.close();
const samples = try readFileF64(self.allocator, samples_file); const samples = try readFileF64(self.allocator, samples_file);
channel.collected_samples = std.ArrayListUnmanaged(f64).fromOwnedSlice(samples); defer self.allocator.free(samples);
try self.project.appendSamples(self.allocator, channel.collected_samples_id, samples);
const stat = try dir.statFile(saved_samples_location); const stat = try dir.statFile(saved_samples_location);
channel.last_sample_save_at = stat.mtime; channel.last_sample_save_at = stat.mtime;
@ -1601,36 +1651,29 @@ pub fn getViewSamples(self: *App, id: Id) []const f64 {
} }
pub fn getViewMinMaxCache(self: *App, id: Id) Graph.MinMaxCache { pub fn getViewMinMaxCache(self: *App, id: Id) Graph.MinMaxCache {
const view = self.getView(id).?; const samples_list_id = self.project.getViewSampleList(id);
const sample_list = self.project.sample_lists.get(samples_list_id).?;
switch (view.reference) { return sample_list.graph_min_max_cache;
.channel => |channel_id| {
const channel = self.getChannel(channel_id).?;
return channel.graph_min_max_cache;
},
.file => |file_id| {
const file = self.getFile(file_id).?;
return file.graph_min_max_cache;
}
}
} }
fn refreshViewAvailableXYRanges(self: *App, id: Id) void { fn refreshViewAvailableXYRanges(self: *App, id: Id) void {
const view = self.getView(id) orelse return; const view = self.getView(id) orelse return;
const sample_list_id = self.project.getViewSampleList(id);
const sample_list = self.project.sample_lists.get(sample_list_id).?;
view.available_x_range = RangeF64.init(0, @floatFromInt(sample_list.samples.items.len));
switch (view.reference) { switch (view.reference) {
.channel => |channel_id| if (self.getChannel(channel_id)) |channel| { .channel => |channel_id| if (self.getChannel(channel_id)) |channel| {
const allowed_sample_values = channel.allowed_sample_values orelse RangeF64.init(0, 0); const allowed_sample_values = channel.allowed_sample_values orelse RangeF64.init(0, 0);
const samples = channel.collected_samples.items;
view.available_x_range = RangeF64.init(0, @floatFromInt(samples.len));
view.available_y_range = RangeF64.init(allowed_sample_values.upper, allowed_sample_values.lower); view.available_y_range = RangeF64.init(allowed_sample_values.upper, allowed_sample_values.lower);
}, },
.file => |file_id| if (self.getFile(file_id)) |file| { .file => {
const samples = file.samples orelse &[_]f64{ }; const min_sample = sample_list.min_sample orelse 0;
const max_sample = sample_list.max_sample orelse 1;
view.available_x_range = RangeF64.init(0, @floatFromInt(samples.len)); view.available_y_range = RangeF64.init(max_sample, min_sample);
view.available_y_range = RangeF64.init(file.max_sample, file.min_sample);
} }
} }
} }

Binary file not shown.

BIN
src/assets/cross.ase Normal file

Binary file not shown.

BIN
src/assets/file.ase Normal file

Binary file not shown.

View File

@ -240,11 +240,20 @@ fn showToolbar(ctx: Context, view_id: Id) void {
.size_x = UI.Sizing.initGrowFull() .size_x = UI.Sizing.initGrowFull()
}); });
_ = ui.createBox(.{
.size_x = UI.Sizing.initFixedPixels(ui.rem(1))
});
const label = ui.label("{s}", .{text}); const label = ui.label("{s}", .{text});
label.size.y = UI.Sizing.initGrowFull(); label.size.y = UI.Sizing.initGrowFull();
label.alignment.x = .center; label.alignment.x = .center;
label.alignment.y = .center; label.alignment.y = .center;
label.padding = UI.Padding.horizontal(ui.rem(1)); // TODO:
// label.padding = UI.Padding.horizontal(ui.rem(1));
_ = ui.createBox(.{
.size_x = UI.Sizing.initFixedPixels(ui.rem(1))
});
} }
} }

View File

@ -191,6 +191,50 @@ pub const RenderCache = struct {
} }
}; };
pub const SampleIterator = struct {
samples: []const f64,
to: f64,
step: f64,
i: f64,
next_i: f64,
pub fn init(samples: []const f64, from: f64, to: f64, step: f64) SampleIterator {
return SampleIterator{
.samples = samples,
.i = from,
.next_i = from + step,
.to = to,
.step = step
};
}
pub fn initRange(samples: []const f64, range: RangeF64, step: f64) SampleIterator {
return SampleIterator.init(samples, range.lower, range.upper, step);
}
pub fn next(self: *SampleIterator) ?[]const f64 {
if (self.next_i < self.to) {
self.i = self.next_i;
self.next_i = self.i + self.step;
const i_usize: usize = @intFromFloat(self.i);
const next_i_usize: usize = @intFromFloat(self.next_i);
if (next_i_usize >= self.samples.len) {
return null;
}
const samples = self.samples[i_usize..next_i_usize];
if (samples.len == 0) {
return null;
}
return samples;
} else {
return null;
}
}
};
fn drawSamplesExact(draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void { fn drawSamplesExact(draw_rect: rl.Rectangle, options: ViewOptions, samples: []const f64) void {
rl.beginScissorMode( rl.beginScissorMode(
@intFromFloat(draw_rect.x), @intFromFloat(draw_rect.x),
@ -241,20 +285,15 @@ fn drawSamplesApproximate(draw_rect: rl.Rectangle, options: ViewOptions, samples
const i_range = options.x_range.intersectPositive( const i_range = options.x_range.intersectPositive(
RangeF64.init(0, @max(@as(f64, @floatFromInt(samples.len)) - 1, 0)) RangeF64.init(0, @max(@as(f64, @floatFromInt(samples.len)) - 1, 0))
); );
if (i_range.lower > i_range.upper) { if (i_range.isNegative()) {
return; return;
} }
const samples_per_column = options.x_range.size() / draw_x_range.size(); const samples_per_column = options.x_range.size() / draw_x_range.size();
assert(samples_per_column >= 1); assert(samples_per_column >= 1);
var i = i_range.lower; var iter = SampleIterator.init(samples, i_range.lower, i_range.upper, samples_per_column);
while (i < i_range.upper - samples_per_column) : (i += samples_per_column) { while (iter.next()) |column_samples| {
const column_start: usize = @intFromFloat(i);
const column_end: usize = @intFromFloat(i + samples_per_column);
const column_samples = samples[column_start..column_end];
if (column_samples.len == 0) continue;
var column_min = column_samples[0]; var column_min = column_samples[0];
var column_max = column_samples[0]; var column_max = column_samples[0];
@ -263,7 +302,7 @@ fn drawSamplesApproximate(draw_rect: rl.Rectangle, options: ViewOptions, samples
column_max = @max(column_max, sample); column_max = @max(column_max, sample);
} }
const x = options.x_range.remapTo(draw_x_range, i); const x = options.x_range.remapTo(draw_x_range, iter.i);
const y_min = options.y_range.remapTo(draw_y_range, column_min); const y_min = options.y_range.remapTo(draw_y_range, column_min);
const y_max = options.y_range.remapTo(draw_y_range, column_max); const y_max = options.y_range.remapTo(draw_y_range, column_max);
@ -290,7 +329,7 @@ fn drawSamplesMinMax(draw_rect: rl.Rectangle, options: ViewOptions, min_max_cach
const i_range = options.x_range.intersectPositive( const i_range = options.x_range.intersectPositive(
RangeF64.init(0, @max(@as(f64, @floatFromInt(min_max_cache.sample_count)) - 1, 0)) RangeF64.init(0, @max(@as(f64, @floatFromInt(min_max_cache.sample_count)) - 1, 0))
); );
if (i_range.lower > i_range.upper) { if (i_range.isNegative()) {
return; return;
} }

View File

@ -112,29 +112,29 @@ pub fn main() !void {
app.project.save_location = save_location; app.project.save_location = save_location;
app.project.sample_rate = 5000; app.project.sample_rate = 5000;
_ = try app.addView(.{ // _ = try app.addView(.{
.channel = try app.addChannel("Dev1/ai0") // .channel = try app.addChannel("Dev1/ai0")
}); // });
_ = try app.addView(.{ // _ = try app.addView(.{
.file = try app.addFile("./samples-5k.bin") // .file = try app.addFile("./samples-5k.bin")
}); // });
_ = try app.addView(.{ // _ = try app.addView(.{
.file = try app.addFile("./samples-50k.bin") // .file = try app.addFile("./samples-50k.bin")
}); // });
_ = try app.addView(.{ // _ = try app.addView(.{
.file = try app.addFile("./samples-300k.bin") // .file = try app.addFile("./samples-300k.bin")
}); // });
_ = try app.addView(.{ // _ = try app.addView(.{
.file = try app.addFile("./samples-9m.bin") // .file = try app.addFile("./samples-9m.bin")
}); // });
_ = try app.addView(.{ // _ = try app.addView(.{
.file = try app.addFile("./samples-18m.bin") // .file = try app.addFile("./samples-18m.bin")
}); // });
} }
var profiler: ?Profiler = null; var profiler: ?Profiler = null;

View File

@ -36,6 +36,7 @@ sample_rate_input: UI.TextInputStorage,
parsed_sample_rate: ?f64 = null, parsed_sample_rate: ?f64 = null,
// View settings // View settings
sliding_window_input: UI.TextInputStorage,
channel_save_file_picker: ?Platform.FilePickerId = null, channel_save_file_picker: ?Platform.FilePickerId = null,
file_save_file_picker: ?Platform.FilePickerId = null, file_save_file_picker: ?Platform.FilePickerId = null,
@ -47,7 +48,8 @@ pub fn init(app: *App) !MainScreen {
.frequency_input = UI.TextInputStorage.init(allocator), .frequency_input = UI.TextInputStorage.init(allocator),
.amplitude_input = UI.TextInputStorage.init(allocator), .amplitude_input = UI.TextInputStorage.init(allocator),
.sample_rate_input = UI.TextInputStorage.init(allocator), .sample_rate_input = UI.TextInputStorage.init(allocator),
.view_controls = ViewControlsSystem.init(&app.project) .view_controls = ViewControlsSystem.init(&app.project),
.sliding_window_input = UI.TextInputStorage.init(allocator)
}; };
try self.frequency_input.setText("10"); try self.frequency_input.setText("10");
@ -317,13 +319,13 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
.label = "Sync controls" .label = "Sync controls"
}); });
var sample_count: ?usize = null; const samples = project.getViewSamples(view_id);
switch (view.reference) { switch (view.reference) {
.channel => |channel_id| { .channel => |channel_id| {
const channel = project.channels.get(channel_id).?; const channel = project.channels.get(channel_id).?;
const channel_name = utils.getBoundedStringZ(&channel.name); const channel_name = utils.getBoundedStringZ(&channel.name);
const channel_type = NIDaq.getChannelType(channel_name); const channel_type = NIDaq.getChannelType(channel_name);
const samples = channel.collected_samples.items;
_ = ui.label("Channel: {s}", .{ channel_name }); _ = ui.label("Channel: {s}", .{ channel_name });
@ -346,16 +348,10 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
channel.saved_collected_samples = path; channel.saved_collected_samples = path;
} }
sample_count = samples.len;
}, },
.file => |file_id| { .file => |file_id| {
const file = project.files.get(file_id).?; const file = project.files.get(file_id).?;
if (file.samples) |samples| {
sample_count = samples.len;
}
if (ui.fileInput(.{ if (ui.fileInput(.{
.key = ui.keyFromString("Filename"), .key = ui.keyFromString("Filename"),
.allocator = self.app.allocator, .allocator = self.app.allocator,
@ -370,18 +366,21 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
} }
if (sample_count != null) { _ = ui.label("Samples: {d}", .{ samples.len });
_ = ui.label("Samples: {d}", .{ sample_count.? });
var duration_str: []const u8 = "-"; var duration_str: []const u8 = "-";
if (sample_rate != null) { if (sample_rate != null) {
const duration = @as(f64, @floatFromInt(sample_count.?)) / sample_rate.?; const duration = @as(f64, @floatFromInt(samples.len)) / sample_rate.?;
if (utils.formatDuration(ui.frameAllocator(), duration)) |str| { if (utils.formatDuration(ui.frameAllocator(), duration)) |str| {
duration_str = str; duration_str = str;
} else |_| {} } else |_| {}
}
_ = ui.label("Duration: {s}", .{ duration_str });
} }
_ = ui.label("Duration: {s}", .{ duration_str });
_ = try ui.numberInput(f64, .{
.key = ui.keyFromString("Sliding window"),
.storage = &self.sliding_window_input
});
} }
fn showMarkedRange(self: *MainScreen, view_id: Id, index: usize) void { fn showMarkedRange(self: *MainScreen, view_id: Id, index: usize) void {