refactor sample list to separate list in project
This commit is contained in:
parent
e5bc57d7b5
commit
af8cf21f53
253
src/app.zig
253
src/app.zig
@ -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 getViewSamples(self: *Project, view_id: Id) []const f64 {
|
pub fn getViewSampleList(self: *Project, view_id: Id) Id {
|
||||||
const empty = &[0]f64{};
|
const view = self.views.get(view_id).?;
|
||||||
|
|
||||||
var result: []const f64 = empty;
|
|
||||||
|
|
||||||
if (self.views.get(view_id)) |view| {
|
|
||||||
switch (view.reference) {
|
switch (view.reference) {
|
||||||
.channel => |channel_id| if (self.channels.get(channel_id)) |channel| {
|
.channel => |channel_id| {
|
||||||
result = channel.collected_samples.items;
|
const channel = self.channels.get(channel_id).?;
|
||||||
|
return channel.collected_samples_id;
|
||||||
},
|
},
|
||||||
.file => |file_id| if (self.files.get(file_id)) |file| {
|
.file => |file_id| {
|
||||||
result = file.samples orelse empty;
|
const file = self.files.get(file_id).?;
|
||||||
|
return file.samples_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
pub fn getViewSamples(self: *Project, view_id: Id) []const f64 {
|
||||||
|
const samples_list_id = self.getViewSampleList(view_id);
|
||||||
|
const samples_list = self.sample_lists.get(samples_list_id).?;
|
||||||
|
|
||||||
|
return samples_list.samples.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn appendSamples(self: *Project, allocator: Allocator, sample_list_id: Id, samples: []const f64) !void {
|
||||||
|
if (samples.len == 0) return;
|
||||||
|
|
||||||
|
const sample_list = self.sample_lists.get(sample_list_id).?;
|
||||||
|
|
||||||
|
const affected_range = RangeF64.init(
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
src/assets/checkbox-mark.ase
Normal file
BIN
src/assets/checkbox-mark.ase
Normal file
Binary file not shown.
BIN
src/assets/cross.ase
Normal file
BIN
src/assets/cross.ase
Normal file
Binary file not shown.
BIN
src/assets/file.ase
Normal file
BIN
src/assets/file.ase
Normal file
Binary file not shown.
@ -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))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
src/main.zig
36
src/main.zig
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user