add proportional views

This commit is contained in:
Rokas Puzonas 2025-05-29 06:45:25 +03:00
parent 6099da25de
commit 68448d1aa3
4 changed files with 598 additions and 279 deletions

View File

@ -194,7 +194,9 @@ const SumRingBuffer = struct {
}
};
pub const Transform = struct {
pub const Transformation = struct {
const Block = SampleList.Block;
const max_gain_changes = 32;
pub const GainChanges = std.BoundedArray(GainChange, max_gain_changes);
pub const GainChange = struct {
@ -202,45 +204,74 @@ pub const Transform = struct {
sample: f64,
};
destination: Id, // Sample list ID
source: Id, // Sample list ID
gain_changes: GainChanges = .{},
divider: ?Id = null, // Sample list ID
pub fn applySlice(self: Transform, index: usize, source: []f64, destination: []f64) void {
assert(source.len == destination.len);
assert(source.len <= SampleList.Block.capacity);
pub fn apply(self: Transformation, project: *Project, from_block: Block.Id, block_count: usize) !void {
const source_list = project.sample_lists.get(self.source) orelse return error.NoSource;
const destination_list = project.sample_lists.get(self.destination) orelse return error.NoDestination;
const gain_changes = self.gain_changes.constSlice();
const gain_segment = Transform.findGainSegment(gain_changes, index);
if (gain_segment.to > @as(f64, @floatFromInt(index+source.len))) {
for (0..source.len) |i| {
destination[i] = source[i] * (100 / gain_segment.gain);
}
} else {
const gain_per_sample = Transform.createGainLookup(gain_changes, index, source.len);
for (0..source.len) |i| {
destination[i] = source[i] * (100 / gain_per_sample[i]);
}
var divider: ?*SampleList = null;
if (self.divider) |divider_id| {
divider = project.sample_lists.get(divider_id);
}
}
pub fn applyBlock(self: Transform, index: usize, source: *SampleList.Block, destination: *SampleList.Block) void {
destination.clear();
destination.len = source.len;
try destination_list.ensureTotalBlocks(@min(from_block + block_count, source_list.blocks.items.len));
self.applySlice(index, source.samplesSlice(), destination.samplesSlice());
destination.recomputeMinMax();
}
var empty_block_buffer: Block.Buffer = undefined;
var empty_block = Block{ .buffer = &empty_block_buffer };
pub fn applySampleList(self: Transform, from_block_index: SampleList.Block.Id, block_count: usize, source: *SampleList, destination: *SampleList) void {
for (0..block_count) |block_offset| {
const block_id = from_block_index + block_offset;
const block_id = from_block + block_offset;
const index = block_id * SampleList.Block.capacity;
const source_block = source.getBlock(block_id).?;
const destination_block = destination.getBlock(block_id).?;
const destination_block = destination_list.getBlock(block_id) orelse continue;
destination_block.clear();
self.applyBlock(block_id * SampleList.Block.capacity, source_block, destination_block);
const source_block = source_list.getBlock(block_id) orelse continue;
destination_block.len = source_block.len;
const source_slice = source_block.samplesSlice();
const destination_slice = destination_block.samplesSlice();
assert(source_block.len == destination_block.len);
assert(source_block.len <= SampleList.Block.capacity);
var divider_block = &empty_block;
if (divider) |sample_list| {
divider_block = sample_list.getBlock(block_id) orelse &empty_block;
}
const divider_slice = divider_block.samplesSlice();
const gain_changes = self.gain_changes.constSlice();
const gain_segment = Transformation.findGainSegment(gain_changes, index);
if (gain_segment.to > @as(f64, @floatFromInt(index+source_slice.len))) {
for (0..source_slice.len) |i| {
destination_slice[i] = source_slice[i] * (100 / gain_segment.gain);
const divider_sample = if (i < divider_slice.len) divider_slice[i] else 1;
if (@abs(divider_sample) > 0.03) {
destination_slice[i] /= divider_sample;
}
}
} else {
const gain_per_sample = Transformation.createGainLookup(gain_changes, index, source_slice.len);
for (0..source_slice.len) |i| {
destination_slice[i] = source_slice[i] * (100 / gain_per_sample[i]);
const divider_sample = if (i < divider_slice.len) divider_slice[i] else 1;
if (@abs(divider_sample) > 0.03) {
destination_slice[i] /= divider_sample;
}
}
}
destination_block.recomputeMinMax();
}
}
@ -309,11 +340,6 @@ pub const Transform = struct {
return result;
}
pub fn eql(self: Transform, other: Transform) bool {
_ = self;
_ = other;
return false;
}
};
pub const SampleList = struct {
@ -577,7 +603,7 @@ pub const SampleList = struct {
}
}
pub fn apply(self: *SampleList, source: *SampleList, from_block_index: Block.Id, block_count: usize, transform: Transform) void {
pub fn apply(self: *SampleList, source: *SampleList, from_block_index: Block.Id, block_count: usize, transform: Transformation) void {
transform.applySampleList(from_block_index, block_count, source, self);
}
@ -728,6 +754,7 @@ pub const File = struct {
pub const View = struct {
pub const Reference = union(enum) {
mirror: Id, // Sample list ID
file: Id,
channel: Id
};
@ -749,8 +776,8 @@ pub const View = struct {
unit: ?NIDaq.Unit = .Voltage,
transformed_samples: Id,
computed_transform: Transform = .{},
invalidated_transform_ranges: std.BoundedArray(RangeF64, 16) = .{},
transformation: Transformation,
dirty_transformation_ranges: std.BoundedArray(RangeF64, 16) = .{},
pub fn clear(self: *View) void {
self.graph_cache.clear();
@ -801,7 +828,7 @@ pub const Project = struct {
protocol_events: std.BoundedArray(ProtocolEvent, 32) = .{},
solutions: std.ArrayListUnmanaged(Solution) = .{},
gain_changes: Transform.GainChanges = .{},
gain_changes: Transformation.GainChanges = .{},
sample_lists: GenerationalArray(SampleList) = .{},
channels: GenerationalArray(Channel) = .{},
@ -851,6 +878,9 @@ pub const Project = struct {
.file => |file_id| {
const file = self.files.get(file_id).?;
return file.samples_id;
},
.mirror => |sample_list_id| {
return sample_list_id;
}
}
}
@ -887,10 +917,19 @@ pub const Project = struct {
return try self.sample_lists.insert(SampleList.init(allocator));
}
pub fn removeSampleList(self: *Project, sample_list_id: Id) void {
pub fn removeSampleList(self: *Project, allocator: std.mem.Allocator, sample_list_id: Id) void {
const sample_list = self.sample_lists.get(sample_list_id) orelse return;
sample_list.deinit();
self.sample_lists.remove(sample_list_id);
var view_iter = self.views.idIterator();
while (view_iter.next()) |view_id| {
const view = self.views.get(view_id).?;
if (view.reference == .mirror and view.reference.mirror.eql(sample_list_id)) {
self.removeView(allocator, view_id);
}
}
}
pub fn readF64SliceFromFile(self: *Project, allocator: Allocator, file: std.fs.File) ![]f64 {
@ -948,7 +987,7 @@ pub const Project = struct {
pub fn removeFile(self: *Project, allocator: std.mem.Allocator, file_id: Id) void {
const file = self.files.get(file_id) orelse return;
self.removeSampleList(file.samples_id);
self.removeSampleList(allocator, file.samples_id);
file.deinit(allocator);
self.files.remove(file_id);
@ -958,7 +997,7 @@ pub const Project = struct {
pub fn removeChannel(self: *Project, allocator: std.mem.Allocator, channel_id: Id) void {
const channel = self.channels.get(channel_id) orelse return;
self.removeSampleList(channel.collected_samples_id);
self.removeSampleList(allocator, channel.collected_samples_id);
channel.deinit(allocator);
self.channels.remove(channel_id);
@ -968,7 +1007,7 @@ pub const Project = struct {
pub fn removeView(self: *Project, allocator: std.mem.Allocator, view_id: Id) void {
const view = self.views.get(view_id) orelse return;
self.removeSampleList(view.transformed_samples);
self.removeSampleList(allocator, view.transformed_samples);
switch (view.reference) {
.file => |file_id| {
@ -976,6 +1015,9 @@ pub const Project = struct {
},
.channel => |channel_id| {
self.removeChannel(allocator, channel_id);
},
.mirror => {
// Do nothing
}
}
@ -1031,13 +1073,20 @@ pub const Project = struct {
}
}
pub fn getViewTransform(self: *Project, view_id: Id) Transform {
pub fn getViewTransform(self: *Project, view_id: Id) Transformation {
_ = view_id;
return Transform{
return Transformation{
.gain_changes = self.gain_changes
};
}
fn invalidateRangeOnAllViews(self: *Project, range: RangeF64) void {
var view_iter = self.views.iterator();
while (view_iter.next()) |view| {
view.dirty_transformation_ranges.append(range) catch continue;
}
}
pub fn removeGainChange(self: *Project, index: usize) void {
const gain_change = self.gain_changes.orderedRemove(index);
@ -1049,14 +1098,11 @@ pub const Project = struct {
const invalidate_range = RangeF64.init(invalidate_lower_bound, invalidate_upper_bound);
var view_iter = self.views.iterator();
while (view_iter.next()) |view| {
view.invalidated_transform_ranges.append(invalidate_range) catch continue;
}
self.invalidateRangeOnAllViews(invalidate_range);
}
pub fn updateGainChange(self: *Project, index: usize, sample_index: f64) void {
const gain_change: *Transform.GainChange = &self.gain_changes.slice()[index];
const gain_change: *Transformation.GainChange = &self.gain_changes.slice()[index];
if (index > 0) {
if (self.gain_changes.buffer[index - 1].sample + SampleList.Block.capacity > sample_index) {
@ -1078,10 +1124,7 @@ pub const Project = struct {
@max(previous_sample_index, sample_index)
);
var view_iter = self.views.iterator();
while (view_iter.next()) |view| {
view.invalidated_transform_ranges.append(invalidated_range) catch continue;
}
self.invalidateRangeOnAllViews(invalidated_range);
}
// ------------------- Serialization ------------------ //
@ -1128,7 +1171,7 @@ pub const Project = struct {
defer allocator.free(channel_name);
const sample_list_id = try self.addSampleList(allocator);
errdefer self.removeSampleList(sample_list_id);
errdefer self.removeSampleList(allocator, sample_list_id);
const channel = self.channels.get(id).?;
channel.* = Channel{
@ -1149,7 +1192,7 @@ pub const Project = struct {
errdefer allocator.free(path);
const sample_list_id = try self.addSampleList(allocator);
errdefer self.removeSampleList(sample_list_id);
errdefer self.removeSampleList(allocator, sample_list_id);
const file = self.files.get(id).?;
file.* = File{
@ -1170,27 +1213,41 @@ pub const Project = struct {
defer allocator.free(name);
const reference_tag = try readInt(reader, u8);
var reference_sample_list_id: Id = undefined;
var divider: ?Id = null;
var reference: View.Reference = undefined;
if (reference_tag == @intFromEnum(View.Reference.file)) {
reference = .{
.file = try readId(reader)
};
const file_id = try readId(reader);
reference = .{ .file = file_id };
reference_sample_list_id = self.files.get(file_id).?.samples_id;
} else if (reference_tag == @intFromEnum(View.Reference.channel)) {
reference = .{
.channel = try readId(reader)
};
const channel_id = try readId(reader);
reference = .{ .channel = channel_id };
reference_sample_list_id = self.channels.get(channel_id).?.collected_samples_id;
} else if (reference_tag == @intFromEnum(View.Reference.mirror)) {
const sample_list_id = try readId(reader);
reference = .{ .mirror = sample_list_id };
reference_sample_list_id = sample_list_id;
divider = try readId(reader);
} else {
return error.InvalidReferenceTag;
}
assert(self.sample_lists.get(reference_sample_list_id) != null);
const sample_list_id = try self.addSampleList(allocator);
errdefer self.removeSampleList(sample_list_id);
errdefer self.removeSampleList(allocator, sample_list_id);
const view = self.views.get(id).?;
view.* = View{
.reference = reference,
.transformed_samples = sample_list_id,
.name = try View.Name.fromSlice(name)
.name = try View.Name.fromSlice(name),
.transformation = Transformation{
.destination = sample_list_id,
.source = reference_sample_list_id,
.divider = divider
}
};
}
}
@ -1243,6 +1300,10 @@ pub const Project = struct {
},
.file => |file_id| {
try writeInt(writer, u32, file_id.asInt());
},
.mirror => |sample_list_id| {
try writeId(writer, sample_list_id);
try writeId(writer, view.transformation.divider.?);
}
}
}
@ -1351,119 +1412,144 @@ pub const CollectionTask = struct {
};
const WorkJob = struct {
const Stage = enum {
init,
launch_threads,
finished
};
view_id: Id,
stage: Stage = .init,
transform: Transform = .{},
transformation: Transformation,
from_block: SampleList.Block.Id,
block_count: usize,
running_threads: std.Thread.WaitGroup = .{},
mutex: std.Thread.Mutex = .{},
running_thread_jobs: std.ArrayListUnmanaged(SampleList.Block.Id) = .{},
processed_up_to: u32 = 0,
pub fn appendRunningThread(self: *WorkJob, allocator: Allocator, block_id: SampleList.Block.Id) !void {
self.mutex.lock();
defer self.mutex.unlock();
try self.running_thread_jobs.append(allocator, block_id);
}
pub fn removeRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) void {
self.mutex.lock();
defer self.mutex.unlock();
if (std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id)) |index| {
_ = self.running_thread_jobs.swapRemove(index);
}
}
pub fn getRunningThreadCount(self: *WorkJob) usize {
self.mutex.lock();
defer self.mutex.unlock();
return self.running_thread_jobs.items.len;
}
pub fn containsRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) bool {
self.mutex.lock();
defer self.mutex.unlock();
return std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id) != null;
pub fn init(transformation: Transformation, from_block: SampleList.Block.Id, block_count: usize) WorkJob {
return WorkJob{
.transformation = transformation,
.from_block = from_block,
.block_count = block_count,
};
}
pub fn deinit(self: *WorkJob, allocator: Allocator) void {
self.running_thread_jobs.deinit(allocator);
_ = self;
_ = allocator;
}
pub fn update(self: *WorkJob, app: *App) !bool {
if (self.stage == .finished) {
return true;
}
const project = &app.project;
const view = project.views.get(self.view_id) orelse return true;
const sample_list_id = project.getViewReferenceSampleListId(self.view_id);
const sample_list = project.sample_lists.get(sample_list_id).?;
if (!Transform.eql(project.getViewTransform(self.view_id), self.transform)) {
// Transforms changed, job needs to be cancelled.
return true;
}
switch (self.stage) {
.init => {
const transformed_samples = project.sample_lists.get(view.transformed_samples).?;
try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len);
self.stage = .launch_threads;
},
.launch_threads => {
const max_block_to_process = 256;
while (self.getRunningThreadCount() < app.work_thread_pool.threads.len and self.processed_up_to < sample_list.blocks.items.len) {
const block_id = self.processed_up_to;
const block_count = @min(sample_list.blocks.items.len - self.processed_up_to, max_block_to_process);
self.processed_up_to += block_count;
try app.work_thread_pool.spawn(workThread, .{ self, project, block_id, block_count });
try self.appendRunningThread(app.allocator, block_id);
}
if (self.processed_up_to == sample_list.blocks.items.len) {
return true;
}
},
.finished => unreachable
}
return false;
}
fn workThread(self: *WorkJob, project: *Project, from_block_id: SampleList.Block.Id, block_count: SampleList.Block.Len) void {
defer self.removeRunningThread(from_block_id);
// var timer = std.time.Timer.start() catch unreachable;
// defer {
// const duration = timer.read();
// std.debug.print("finished {d:.5}ms\n", .{ @as(f64, @floatFromInt(duration)) / std.time.ns_per_ms });
// }
const view = project.views.get(self.view_id) orelse return;
const transformed_samples = project.sample_lists.get(view.transformed_samples) orelse return;
const sample_list_id = project.getViewReferenceSampleListId(self.view_id);
const sample_list = project.sample_lists.get(sample_list_id) orelse return;
transformed_samples.apply(sample_list, from_block_id, block_count, self.transform);
pub fn tick(self: *WorkJob) void {
_ = self;
}
};
// const WorkJob = struct {
// const Stage = enum {
// init,
// launch_threads,
// finished
// };
// view_id: Id,
// stage: Stage = .init,
// transform: Transform = .{},
// mutex: std.Thread.Mutex = .{},
// running_thread_jobs: std.ArrayListUnmanaged(SampleList.Block.Id) = .{},
// processed_up_to: u32 = 0,
// pub fn appendRunningThread(self: *WorkJob, allocator: Allocator, block_id: SampleList.Block.Id) !void {
// self.mutex.lock();
// defer self.mutex.unlock();
// try self.running_thread_jobs.append(allocator, block_id);
// }
// pub fn removeRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) void {
// self.mutex.lock();
// defer self.mutex.unlock();
// if (std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id)) |index| {
// _ = self.running_thread_jobs.swapRemove(index);
// }
// }
// pub fn getRunningThreadCount(self: *WorkJob) usize {
// self.mutex.lock();
// defer self.mutex.unlock();
// return self.running_thread_jobs.items.len;
// }
// pub fn containsRunningThread(self: *WorkJob, block_id: SampleList.Block.Id) bool {
// self.mutex.lock();
// defer self.mutex.unlock();
// return std.mem.indexOfScalar(SampleList.Block.Id, self.running_thread_jobs.items, block_id) != null;
// }
// pub fn deinit(self: *WorkJob, allocator: Allocator) void {
// self.running_thread_jobs.deinit(allocator);
// }
// pub fn update(self: *WorkJob, app: *App) !bool {
// if (self.stage == .finished) {
// return true;
// }
// const project = &app.project;
// const view = project.views.get(self.view_id) orelse return true;
// const sample_list_id = project.getViewReferenceSampleListId(self.view_id);
// const sample_list = project.sample_lists.get(sample_list_id).?;
// if (!Transform.eql(project.getViewTransform(self.view_id), self.transform)) {
// // Transforms changed, job needs to be cancelled.
// return true;
// }
// switch (self.stage) {
// .init => {
// const transformed_samples = project.sample_lists.get(view.transformed_samples).?;
// try transformed_samples.reserveEmptyBlocks(sample_list.blocks.items.len);
// self.stage = .launch_threads;
// },
// .launch_threads => {
// const max_block_to_process = 256;
// while (self.getRunningThreadCount() < app.work_thread_pool.threads.len and self.processed_up_to < sample_list.blocks.items.len) {
// const block_id = self.processed_up_to;
// const block_count = @min(sample_list.blocks.items.len - self.processed_up_to, max_block_to_process);
// self.processed_up_to += block_count;
// try app.work_thread_pool.spawn(workThread, .{ self, project, block_id, block_count });
// try self.appendRunningThread(app.allocator, block_id);
// }
// if (self.processed_up_to == sample_list.blocks.items.len) {
// return true;
// }
// },
// .finished => unreachable
// }
// return false;
// }
// fn workThread(self: *WorkJob, project: *Project, from_block_id: SampleList.Block.Id, block_count: SampleList.Block.Len) void {
// defer self.removeRunningThread(from_block_id);
// // var timer = std.time.Timer.start() catch unreachable;
// // defer {
// // const duration = timer.read();
// // std.debug.print("finished {d:.5}ms\n", .{ @as(f64, @floatFromInt(duration)) / std.time.ns_per_ms });
// // }
// const view = project.views.get(self.view_id) orelse return;
// const transformed_samples = project.sample_lists.get(view.transformed_samples) orelse return;
// const sample_list_id = project.getViewReferenceSampleListId(self.view_id);
// const sample_list = project.sample_lists.get(sample_list_id) orelse return;
// transformed_samples.apply(sample_list, from_block_id, block_count, self.transform);
// }
// };
allocator: Allocator,
ui: UI,
double_pass_ui: bool = true,
@ -1857,37 +1943,78 @@ pub fn tick(self: *App) !void {
try self.showUI();
{
const block_size = SampleList.Block.capacity;
var view_iter = self.project.views.idIterator();
while (view_iter.next()) |view_id| {
const view = self.getView(view_id).?;
view.transformation.gain_changes = self.project.gain_changes;
const referenced_samples_id = self.project.getViewReferenceSampleListId(view_id);
const referenced_samples = self.project.sample_lists.get(referenced_samples_id).?;
const computed_samples_id = view.transformed_samples;
const computed_samples = self.project.sample_lists.get(computed_samples_id).?;
const source_id = view.transformation.source;
const source = self.project.sample_lists.get(source_id).?;
const source_len = source.getLength();
const transform = self.project.getViewTransform(view_id);
const computed_len = computed_samples.getLength();
const reference_len = referenced_samples.getLength();
const destination_id = view.transformation.destination;
const destination = self.project.sample_lists.get(destination_id).?;
const destination_len = destination.getLength();
if (reference_len > computed_len) {
view.invalidated_transform_ranges.append(RangeF64.init(@floatFromInt(computed_len), @floatFromInt(reference_len))) catch {};
if (source_len != destination_len) {
if (view.transformation.divider) |divider| {
const divider_list = self.project.sample_lists.get(divider).?;
if (divider_list.getLength() < source_len) {
continue;
}
}
const source_len_f64: f64 = @max(0, @as(f64, @floatFromInt(source_len)) - 1);
const destination_len_f64: f64 = @max(0, @as(f64, @floatFromInt(destination_len)) - 1);
const range = RangeF64.init(@min(source_len_f64, destination_len_f64), @max(source_len_f64, destination_len_f64));
view.dirty_transformation_ranges.append(range) catch {};
// var other_view_iter = self.project.views.idIterator();
// while (other_view_iter.next()) |other_view_id| {
// const other_view = self.project.views.get(other_view_id).?;
// if (other_view.reference == .mirror and other_view.reference.mirror.eql(source_id)) {
// view.dirty_transformation_ranges.append(range) catch {};
// }
// }
}
}
}
{
const block_size = SampleList.Block.capacity;
var view_iter = self.project.views.iterator();
while (view_iter.next()) |view| {
const source_id = view.transformation.source;
const source = self.project.sample_lists.get(source_id).?;
const source_len = source.getLength();
var last_source_block: usize = 0;
if (source_len > 0) {
last_source_block = source_len - 1;
}
for (view.invalidated_transform_ranges.constSlice()) |range| {
const from_block: usize = @intFromFloat(@divFloor(range.lower, block_size));
const to_block: usize = @intFromFloat(@divFloor(@min(range.upper, @as(f64, @floatFromInt(reference_len)) - 1), block_size));
if (from_block > to_block) continue;
for (view.dirty_transformation_ranges.constSlice()) |range| {
var from_block = last_source_block;
if (!std.math.isInf(range.lower) and range.lower >= 0) {
from_block = @intFromFloat(@divFloor(range.lower, block_size));
}
var to_block = last_source_block;
if (!std.math.isInf(range.upper) and range.upper >= 0) {
to_block = @intFromFloat(@divFloor(range.upper, block_size));
}
if (from_block > to_block) {
continue;
}
const block_count = to_block - from_block + 1;
try computed_samples.ensureTotalBlocks(from_block + block_count);
transform.applySampleList(@intCast(from_block), block_count, referenced_samples, computed_samples);
view.graph_cache.invalidateRange(range);
try view.transformation.apply(&self.project, from_block, block_count);
view.graph_cache.invalidate();
}
view.invalidated_transform_ranges.len = 0;
view.dirty_transformation_ranges.len = 0;
}
}
}
@ -1954,43 +2081,43 @@ pub fn tick(self: *App) !void {
}
}
{
var view_iter = self.project.views.idIterator();
while (view_iter.next()) |view_id| {
const view = self.project.views.get(view_id).?;
_ = view;
// {
// var view_iter = self.project.views.idIterator();
// while (view_iter.next()) |view_id| {
// const view = self.project.views.get(view_id).?;
// _ = view;
// TODO:
// if (Transform.eqlSlice(view.computed_transforms.constSlice(), view.transforms.constSlice())) {
// continue;
// }
// // TODO:
// // if (Transform.eqlSlice(view.computed_transforms.constSlice(), view.transforms.constSlice())) {
// // continue;
// // }
// _ = try self.work_jobs.insert(WorkJob{
// .transforms = view.transforms,
// .view_id = view_id
// });
// // _ = try self.work_jobs.insert(WorkJob{
// // .transforms = view.transforms,
// // .view_id = view_id
// // });
// view.computed_transforms.len = 0;
// view.computed_transforms.appendSliceAssumeCapacity(view.transforms.constSlice());
}
}
// // view.computed_transforms.len = 0;
// // view.computed_transforms.appendSliceAssumeCapacity(view.transforms.constSlice());
// }
// }
{
var work_job_iter = self.work_jobs.idIterator();
while (work_job_iter.next()) |work_job_id| {
const work_job = self.work_jobs.get(work_job_id).?;
const job_done = try work_job.update(self);
// {
// var work_job_iter = self.work_jobs.idIterator();
// while (work_job_iter.next()) |work_job_id| {
// const work_job = self.work_jobs.get(work_job_id).?;
// const job_done = try work_job.update(self);
if (job_done) {
work_job.stage = .finished;
if (work_job.getRunningThreadCount() == 0) {
std.debug.print("job done {}\n", .{work_job_id});
work_job.deinit(self.allocator);
self.work_jobs.remove(work_job_id);
}
}
}
}
// if (job_done) {
// work_job.stage = .finished;
// if (work_job.getRunningThreadCount() == 0) {
// std.debug.print("job done {}\n", .{work_job_id});
// work_job.deinit(self.allocator);
// self.work_jobs.remove(work_job_id);
// }
// }
// }
// }
}
pub fn pushCommand(self: *App, command: Command) void {
@ -2268,7 +2395,7 @@ pub fn addFile(self: *App, path: []const u8) !Id {
errdefer self.allocator.free(path_dupe);
const sample_list_id = try self.project.addSampleList(self.allocator);
errdefer self.project.removeSampleList(sample_list_id);
errdefer self.project.removeSampleList(self.allocator, sample_list_id);
const file = self.project.files.get(id).?;
file.* = File{
@ -2314,7 +2441,7 @@ pub fn addChannel(self: *App, channel_name: []const u8) !Id {
errdefer self.project.channels.remove(id);
const sample_list_id = try self.project.addSampleList(self.allocator);
errdefer self.project.removeSampleList(sample_list_id);
errdefer self.project.removeSampleList(self.allocator, sample_list_id);
const channel = self.project.channels.get(id).?;
channel.* = Channel{
@ -2412,12 +2539,23 @@ pub fn addView(self: *App, reference: View.Reference) !Id {
errdefer self.project.views.remove(id);
const sample_list_id = try self.project.addSampleList(self.allocator);
errdefer self.project.removeSampleList(sample_list_id);
errdefer self.project.removeSampleList(self.allocator, sample_list_id);
const reference_sample_list_id = switch (reference) {
.channel => |channel_id| self.project.channels.get(channel_id).?.collected_samples_id,
.file => |file_id| self.project.files.get(file_id).?.samples_id,
.mirror => |list_id| list_id,
};
assert(self.project.sample_lists.get(reference_sample_list_id) != null);
const view = self.project.views.get(id).?;
view.* = View{
.reference = reference,
.transformed_samples = sample_list_id
.transformed_samples = sample_list_id,
.transformation = .{
.source = reference_sample_list_id,
.destination = sample_list_id,
}
};
self.loadView(id) catch |e| {
@ -2433,6 +2571,9 @@ fn refreshViewAvailableXYRanges(self: *App, id: Id) void {
const sample_list = self.project.sample_lists.get(sample_list_id).?;
view.available_x_range = RangeF64.init(0, @floatFromInt(sample_list.getLength()));
if (view.available_x_range.size() == 0) {
view.available_x_range = RangeF64.init(0, 1);
}
switch (view.reference) {
.channel => |channel_id| if (self.getChannel(channel_id)) |channel| {
@ -2444,6 +2585,9 @@ fn refreshViewAvailableXYRanges(self: *App, id: Id) void {
const min_sample = sample_list.min orelse 0;
const max_sample = sample_list.max orelse 1;
view.available_y_range = RangeF64.init(max_sample, min_sample);
},
.mirror => {
view.available_y_range = RangeF64.init(sample_list.max orelse 10, sample_list.min orelse -10);
}
}
}

View File

@ -163,62 +163,67 @@ fn showToolbar(ctx: Context, view_id: Id) void {
}
}
if (view.reference == .channel) {
const channel_id = view.reference.channel;
const channel = ctx.app.getChannel(channel_id).?;
const channel_name = utils.getBoundedStringZ(&channel.name);
// const channel_type = NIDaq.getChannelType(channel_name).?;
switch (view.reference) {
.channel => |channel_id| {
const channel = ctx.app.getChannel(channel_id).?;
const channel_name = utils.getBoundedStringZ(&channel.name);
// const channel_type = NIDaq.getChannelType(channel_name).?;
{
const follow = ui.textButton("Follow");
follow.background = srcery.hard_black;
if (view.follow) {
follow.borders = UI.Borders.bottom(.{
.color = srcery.green,
.size = 4
});
}
if (ui.signal(follow).clicked()) {
view.follow = !view.follow;
{
const follow = ui.textButton("Follow");
follow.background = srcery.hard_black;
if (view.follow) {
follow.borders = UI.Borders.bottom(.{
.color = srcery.green,
.size = 4
});
}
if (ui.signal(follow).clicked()) {
view.follow = !view.follow;
}
}
// if (channel_type == .analog_output) {
// const button = ui.button(ui.keyFromString("Output generation"));
// button.texture = Assets.output_generation;
// button.size.y = UI.Sizing.initGrowFull();
// const signal = ui.signal(button);
// if (signal.clicked()) {
// if (ctx.app.isChannelOutputing(channel_id)) {
// ctx.app.pushCommand(.{
// .stop_output = channel_id
// });
// } else {
// ctx.view_controls.view_protocol_modal = view_id;
// }
// }
// var color = rl.Color.white;
// if (ctx.app.isChannelOutputing(channel_id)) {
// color = srcery.red;
// }
// if (signal.active) {
// button.texture_color = color.alpha(0.6);
// } else if (signal.hot) {
// button.texture_color = color.alpha(0.8);
// } else {
// button.texture_color = color;
// }
// }
view_name = channel_name;
},
.file => |file_id| {
const file = ctx.app.getFile(file_id).?;
view_name = std.fs.path.stem(file.path);
},
.mirror => |sample_list_id| {
_ = sample_list_id;
view_name = "Proportional";
}
// if (channel_type == .analog_output) {
// const button = ui.button(ui.keyFromString("Output generation"));
// button.texture = Assets.output_generation;
// button.size.y = UI.Sizing.initGrowFull();
// const signal = ui.signal(button);
// if (signal.clicked()) {
// if (ctx.app.isChannelOutputing(channel_id)) {
// ctx.app.pushCommand(.{
// .stop_output = channel_id
// });
// } else {
// ctx.view_controls.view_protocol_modal = view_id;
// }
// }
// var color = rl.Color.white;
// if (ctx.app.isChannelOutputing(channel_id)) {
// color = srcery.red;
// }
// if (signal.active) {
// button.texture_color = color.alpha(0.6);
// } else if (signal.hot) {
// button.texture_color = color.alpha(0.8);
// } else {
// button.texture_color = color;
// }
// }
view_name = channel_name;
} else if (view.reference == .file) {
const file_id = view.reference.file;
const file = ctx.app.getFile(file_id).?;
view_name = std.fs.path.stem(file.path);
}
if (view.name.len > 0) {

View File

@ -12,7 +12,7 @@ const raylib_h = @cImport({
});
const log = std.log;
const profiler_enabled = builtin.mode == .Debug;
const profiler_enabled = true; //builtin.mode == .Debug;
// TODO: Maybe move this to a config.zig or options.zig file.
// Have all of the contstants in a single file.

View File

@ -73,6 +73,10 @@ view_name_input: UI.TextInputStorage,
channel_save_file_picker: ?Platform.FilePickerId = null,
file_save_file_picker: ?Platform.FilePickerId = null,
// Proportional modal
proportion_view1: ?Id = null, // View ID
proportion_view2: ?Id = null, // View ID
pub fn init(app: *App) !MainScreen {
const allocator = app.allocator;
@ -422,7 +426,7 @@ fn showProportionalAdd(self: *MainScreen) !void {
.key = ui.keyFromString("Proportional add modal"),
.background = srcery.black,
.size_x = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
.size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
.size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 200 }),
.layout_direction = .top_to_bottom,
.padding = UI.Padding.all(ui.rem(1.5)),
.flags = &.{ .clickable },
@ -431,6 +435,169 @@ fn showProportionalAdd(self: *MainScreen) !void {
container.beginChildren();
defer container.endChildren();
defer _ = ui.signal(container);
if (self.proportion_view1 != null and self.app.project.views.get(self.proportion_view1.?) == null) {
self.proportion_view1 = null;
}
if (self.proportion_view2 != null and self.app.project.views.get(self.proportion_view2.?) == null) {
self.proportion_view2 = null;
}
var arena = std.heap.ArenaAllocator.init(self.app.allocator);
defer arena.deinit();
var available_views = std.ArrayList(struct { view_id: App.Id, name: []const u8 }).init(arena.allocator());
{
var view_iter = self.app.project.views.idIterator();
while (view_iter.next()) |view_id| {
const view = self.app.project.views.get(view_id).?;
if (view.reference != .channel) {
continue;
}
const channel_id = view.reference.channel;
const channel = self.app.project.channels.get(channel_id).?;
const view_name = utils.getBoundedStringZ(&channel.name);
try available_views.append(.{ .view_id = view_id, .name = view_name });
}
}
{
var row = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFitChildren(),
.layout_direction = .left_to_right,
.layout_gap = ui.rem(0.5),
});
row.beginChildren();
defer row.endChildren();
{
const select = ui.textButton("Select dividend");
if (self.proportion_view1) |view_id| {
const view = self.app.project.views.get(view_id).?;
assert(view.reference == .channel);
const channel = self.app.project.channels.get(view.reference.channel).?;
select.setFmtText("Dividend: {s}", .{utils.getBoundedStringZ(&channel.name)});
}
select.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 });
const signal = ui.signal(select);
if (signal.clicked()) {
select.persistent.open = !select.persistent.open;
}
if (select.persistent.open) {
const popup = ui.createBox(.{
.key = ui.keyFromString("Transform 1 popup"),
.size_x = UI.Sizing.initFixedPixels(ui.rem(10)),
.size_y = UI.Sizing.initFitChildren(),
.flags = &.{ .clickable, .scrollable },
.layout_direction = .top_to_bottom,
.float_relative_to = select,
.background = srcery.black,
.borders = UI.Borders.all(.{ .color = srcery.bright_black, .size = 4 }),
.draw_on_top = true
});
popup.setFloatPosition(0, select.persistent.size.y);
popup.beginChildren();
defer popup.endChildren();
defer _ = ui.signal(popup);
for (available_views.items) |option| {
const select_option = ui.textButton(option.name);
select_option.alignment.x = .start;
select_option.size.x = UI.Sizing.initGrowFull();
select_option.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 });
select_option.background = srcery.black;
if (ui.signal(select_option).clicked()) {
select.persistent.open = false;
self.proportion_view1 = option.view_id;
}
}
}
if (signal.clicked_outside) {
select.persistent.open = false;
}
}
{
const select = ui.textButton("Select divisor");
if (self.proportion_view2) |view_id| {
const view = self.app.project.views.get(view_id).?;
assert(view.reference == .channel);
const channel = self.app.project.channels.get(view.reference.channel).?;
select.setFmtText("Divisor: {s}", .{utils.getBoundedStringZ(&channel.name)});
}
select.borders = UI.Borders.all(.{ .color = srcery.blue, .size = 4 });
const signal = ui.signal(select);
if (signal.clicked()) {
select.persistent.open = !select.persistent.open;
}
if (select.persistent.open) {
const popup = ui.createBox(.{
.key = ui.keyFromString("Transform 1 popup"),
.size_x = UI.Sizing.initFixedPixels(ui.rem(10)),
.size_y = UI.Sizing.initFitChildren(),
.flags = &.{ .clickable, .scrollable },
.layout_direction = .top_to_bottom,
.float_relative_to = select,
.background = srcery.black,
.borders = UI.Borders.all(.{ .color = srcery.bright_black, .size = 4 }),
.draw_on_top = true
});
popup.setFloatPosition(0, select.persistent.size.y);
popup.beginChildren();
defer popup.endChildren();
defer _ = ui.signal(popup);
for (available_views.items) |option| {
const select_option = ui.textButton(option.name);
select_option.alignment.x = .start;
select_option.size.x = UI.Sizing.initGrowFull();
select_option.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 });
select_option.background = srcery.black;
if (ui.signal(select_option).clicked()) {
select.persistent.open = false;
self.proportion_view2 = option.view_id;
}
}
}
if (signal.clicked_outside) {
select.persistent.open = false;
}
}
}
{
const btn = ui.textButton("Confirm");
btn.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 4 });
if (self.proportion_view1 != null and self.proportion_view2 != null) {
btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
if (ui.signal(btn).clicked()) {
self.modal = null;
const view1 = self.app.project.views.get(self.proportion_view1.?).?;
const channel1 = self.app.project.channels.get(view1.reference.channel).?;
const view2 = self.app.project.views.get(self.proportion_view2.?).?;
const channel2 = self.app.project.channels.get(view2.reference.channel).?;
const new_view_id = try self.app.addView(.{ .mirror = channel1.collected_samples_id });
const new_view = self.app.project.views.get(new_view_id).?;
new_view.transformation.divider = channel2.collected_samples_id;
}
}
}
}
// fn setProtocolErrorMessage(self: *MainScreen, comptime fmt: []const u8, args: anytype) !void {
@ -742,6 +909,9 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
file.path = path;
self.app.pushCommand(.{ .reload_file = file_id });
}
},
.mirror => |sample_list_id| {
_ = sample_list_id;
}
}