add saving of project settings

This commit is contained in:
Rokas Puzonas 2025-04-08 21:36:13 +03:00
parent d2b4942fa0
commit 293d220b34
4 changed files with 327 additions and 25 deletions

View File

@ -2,13 +2,20 @@
.name = "Baigiamasis projektas",
.version = "0.1.0",
.dependencies = .{ .@"raylib-zig" = .{ .url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz", .hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212" }, .@"known-folders" = .{
.dependencies = .{
.@"raylib-zig" = .{
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
},
.@"known-folders" = .{
.url = "git+https://github.com/ziglibs/known-folders.git#1cceeb70e77dec941a4178160ff6c8d05a74de6f",
.hash = "12205f5e7505c96573f6fc5144592ec38942fb0a326d692f9cddc0c7dd38f9028f29",
}, .ini = .{
},
.ini = .{
.url = "https://github.com/ziglibs/ini/archive/e18d36665905c1e7ba0c1ce3e8780076b33e3002.tar.gz",
.hash = "1220b0979ea9891fa4aeb85748fc42bc4b24039d9c99a4d65d893fb1c83e921efad8",
}, .@"profiler.zig" = .{
},
.@"profiler.zig" = .{
.url = "git+https://github.com/lassade/profiler.zig.git#d066d066c36c4eebd494babf15c1cdbd2d512b12",
.hash = "122097461acc2064f5f89b85d76d2a02232579864b17604617a333789c892f2d262f",
},

View File

@ -28,6 +28,10 @@ pub const Id = packed struct {
return @bitCast(self);
}
pub fn fromInt(self: u32) Id {
return @bitCast(self);
}
pub fn eql(self: Id, other: Id) bool {
return @as(u32, @bitCast(self)) == @as(u32, @bitCast(other));
}
@ -78,6 +82,16 @@ fn GenerationalArray(Item: type) type {
used: UsedBitSet = UsedBitSet.initFull(),
items: [Id.max_items]GenerationalItem = undefined,
pub fn insertUndefinedAt(self: *Self, id: Id) !void {
if (!self.used.isSet(id.index)) {
return error.NotEmpty;
}
self.used.unset(id.index);
self.items[id.index].generation = id.generation;
}
pub fn insertUndefined(self: *Self) !Id {
const index: Id.Index = @intCast(self.used.findFirstSet() orelse return error.OutOfMemory);
@ -96,11 +110,11 @@ fn GenerationalArray(Item: type) type {
}
pub fn remove(self: *Self, id: Id) void {
if (self.used.isSet(id.generation)) {
if (self.used.isSet(id.index)) {
return;
}
self.used.set(id.generation);
self.used.set(id.index);
self.items[id.index].generation += 1;
}
@ -113,7 +127,7 @@ fn GenerationalArray(Item: type) type {
}
pub fn has(self: *Self, id: Id) bool {
if (self.used.isSet(id.generation)) {
if (self.used.isSet(id.index)) {
return false;
}
@ -152,9 +166,9 @@ pub const Channel = struct {
// Persistent
name: Name = .{},
device: Device = .{},
// Runtime
device: Device = .{},
allowed_sample_values: ?RangeF64 = null,
allowed_sample_rates: ?RangeF64 = null,
collected_samples: std.ArrayListUnmanaged(f64) = .{},
@ -263,6 +277,10 @@ pub const View = struct {
};
pub const Project = struct {
const file_endian = std.builtin.Endian.big;
const file_format_version: u8 = 0;
save_location: ?[]u8 = null,
sample_rate: ?f64 = null,
channels: GenerationalArray(Channel) = .{},
@ -307,6 +325,215 @@ pub const Project = struct {
while (channel_iter.next()) |channel| {
channel.deinit(allocator);
}
if (self.save_location) |str| {
allocator.free(str);
self.save_location = null;
}
}
// ------------------- Serialization ------------------ //
pub fn initFromFile(allocator: Allocator, save_location: []const u8) !Project {
var self = Project{};
const f = try std.fs.cwd().openFile(save_location, .{});
defer f.close();
const reader = f.reader();
const version = try readInt(reader, u8);
if (version != file_format_version) {
return error.VersionMismatch;
}
self.sample_rate = try readFloat(reader, f64);
if (self.sample_rate == 0) {
self.sample_rate = null;
}
{ // Channels
const channel_count = try readInt(reader, u32);
for (0..channel_count) |_| {
const id = try readId(reader);
try self.channels.insertUndefinedAt(id);
const channel_name = try readString(reader, allocator);
defer allocator.free(channel_name);
const channel = self.channels.get(id).?;
channel.* = Channel{
.name = try utils.initBoundedStringZ(Channel.Name, channel_name)
};
}
}
{ // Files
const file_count = try readInt(reader, u32);
for (0..file_count) |_| {
const id = try readId(reader);
try self.files.insertUndefinedAt(id);
const path = try readString(reader, allocator);
errdefer allocator.free(path);
const file = self.files.get(id).?;
file.* = File{
.path = path
};
}
}
{ // Views
const view_count = try readInt(reader, u32);
for (0..view_count) |_| {
const id = try readId(reader);
try self.views.insertUndefinedAt(id);
const reference_tag = try readInt(reader, u8);
var reference: View.Reference = undefined;
if (reference_tag == @intFromEnum(View.Reference.file)) {
reference = .{
.file = try readId(reader)
};
} else if (reference_tag == @intFromEnum(View.Reference.channel)) {
reference = .{
.channel = try readId(reader)
};
} else {
return error.InvalidReferenceTag;
}
const view = self.views.get(id).?;
view.* = View{
.reference = reference,
};
view.graph_opts.x_range = try readRangeF64(reader);
view.graph_opts.y_range = try readRangeF64(reader);
}
}
self.save_location = try allocator.dupe(u8, save_location);
errdefer allocator.free(self.save_location);
return self;
}
pub fn save(self: *Project) !void {
const save_location = self.save_location orelse return error.NoSaveLocation;
const f = try std.fs.cwd().createFile(save_location, .{});
defer f.close();
const writer = f.writer();
try writeInt(writer, u8, file_format_version);
try writeFloat(writer, f64, self.sample_rate orelse 0);
{ // Channels
try writeInt(writer, u32, @intCast(self.channels.count()));
var channel_iter = self.channels.idIterator();
while (channel_iter.next()) |channel_id| {
const channel = self.channels.get(channel_id).?;
const channel_name = utils.getBoundedStringZ(&channel.name);
try writeId(writer, channel_id);
try writeString(writer, channel_name);
}
}
{ // Files
try writeInt(writer, u32, @intCast(self.files.count()));
var file_iter = self.files.idIterator();
while (file_iter.next()) |file_id| {
const file = self.files.get(file_id).?;
try writeId(writer, file_id);
try writeString(writer, file.path);
}
}
{ // Views
try writeInt(writer, u32, @intCast(self.views.count()));
var view_iter = self.views.idIterator();
while (view_iter.next()) |view_id| {
const view = self.views.get(view_id).?;
try writeId(writer, view_id);
try writeInt(writer, u8, @intFromEnum(view.reference));
switch (view.reference) {
.channel => |channel_id| {
try writeInt(writer, u32, channel_id.asInt());
},
.file => |file_id| {
try writeInt(writer, u32, file_id.asInt());
}
}
try writeRangeF64(writer, view.graph_opts.x_range);
try writeRangeF64(writer, view.graph_opts.y_range);
}
}
}
fn writeRangeF64(writer: anytype, range: RangeF64) !void {
try writeFloat(writer, f64, range.lower);
try writeFloat(writer, f64, range.upper);
}
fn readRangeF64(writer: anytype) !RangeF64 {
var range: RangeF64 = undefined;
range.lower = try readFloat(writer, f64);
range.upper = try readFloat(writer, f64);
return range;
}
fn readInt(reader: anytype, T: type) !T {
return try reader.readInt(T, file_endian);
}
fn writeInt(writer: anytype, T: type, value: T) !void {
try writer.writeInt(T, value, file_endian);
}
fn readId(reader: anytype) !Id {
const id_u32 = try readInt(reader, u32);
return Id.fromInt(id_u32);
}
fn writeId(writer: anytype, value: Id) !void {
try writer.writeInt(u32, value.asInt(), file_endian);
}
fn writeFloat(writer: anytype, T: type, value: T) !void {
const bytes = std.mem.asBytes(&value);
try writer.writeAll(bytes);
}
fn readFloat(reader: anytype, T: type) !T {
var buff: [@sizeOf(T)]u8 = undefined;
try reader.readNoEof(&buff);
return std.mem.bytesToValue(T, &buff);
}
fn writeString(writer: anytype, text: []const u8) !void {
try writeInt(writer, u32, @intCast(text.len));
try writer.writeAll(text);
}
fn readString(reader: anytype, allocator: Allocator) ![]u8 {
// TODO: This could be risky. `str_len` can be a really large number and in turn request a really large allocation.
const str_len = try readInt(reader, u32);
const buff = try allocator.alloc(u8, str_len);
errdefer allocator.free(buff);
try reader.readNoEof(buff);
return buff;
}
};
@ -314,6 +541,7 @@ pub const Command = union(enum) {
start_collection,
stop_collection,
save_project,
load_project,
stop_output: Id, // Channel id
start_output: Id, // Channel id
add_file_from_picker
@ -390,11 +618,14 @@ pub fn init(self: *App, allocator: Allocator) !void {
}
pub fn deinit(self: *App) void {
self.stopCollection();
self.deinitProject();
self.should_close = true;
self.collection_condition.signal();
self.collection_thread.join();
self.ui.deinit();
self.main_screen.deinit();
self.project.deinit(self.allocator);
if (self.ni_daq) |*ni_daq| {
ni_daq.deinit(self.allocator);
@ -405,9 +636,50 @@ pub fn deinit(self: *App) void {
}
}
pub fn loadProject(self: *App, project_file: []const u8) !void {
_ = self;
_ = project_file;
pub fn deinitProject(self: *App) void {
self.stopCollection();
self.project.deinit(self.allocator);
}
pub fn loadProject(self: *App) !void {
const save_location = self.project.save_location orelse return error.MissingSaveLocation;
log.info("Load project from: {s}", .{save_location});
const loaded = try Project.initFromFile(self.allocator, save_location);
errdefer loaded.deinit(self.allocator);
self.deinitProject();
self.project = loaded;
var file_iter = self.project.files.idIterator();
while (file_iter.next()) |file_id| {
self.loadFile(file_id) catch |e| {
log.err("Failed to load file: {}", .{ e });
};
}
var channel_iter = self.project.channels.idIterator();
while (channel_iter.next()) |channel_id| {
self.loadChannel(channel_id) catch |e| {
log.err("Failed to load channel: {}", .{ e });
};
}
var view_iter = self.project.views.idIterator();
while (view_iter.next()) |view_id| {
self.loadView(view_id) catch |e| {
log.err("Failed to load view: {}", .{ e });
};
}
}
pub fn saveProject(self: *App) !void {
const save_location = self.project.save_location orelse return error.MissingSaveLocation;
log.info("Save project to: {s}", .{save_location});
try self.project.save();
}
pub fn tick(self: *App) !void {
@ -415,6 +687,15 @@ pub fn tick(self: *App) !void {
self.command_queue.len = 0;
ui.pullOsEvents();
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_s)) {
self.pushCommand(.save_project);
}
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_l)) {
self.pushCommand(.load_project);
}
{
self.collection_samples_mutex.lock();
defer self.collection_samples_mutex.unlock();
@ -447,10 +728,6 @@ pub fn tick(self: *App) !void {
}
}
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_s)) {
self.pushCommand(.save_project);
}
ui.begin();
defer ui.end();
@ -473,7 +750,15 @@ pub fn tick(self: *App) !void {
self.stopCollection();
},
.save_project => {
// TODO:
self.saveProject() catch |e| {
log.err("Failed to save project: {}", .{e});
};
},
.load_project => {
self.loadProject() catch |e| {
log.err("Failed to load project: {}", .{e});
utils.dumpErrorTrace();
};
},
.add_file_from_picker => {
self.addFileFromPicker() catch |e| {

View File

@ -102,10 +102,6 @@ pub fn main() !void {
defer app.deinit();
if (builtin.mode == .Debug) {
// const cwd_path = try std.fs.cwd().realpathAlloc(allocator, ".");
// defer allocator.free(cwd_path);
// try app.loadProject(cwd_path);
_ = try app.addView(.{
.channel = try app.addChannel("Dev1/ai0")
});
@ -116,6 +112,14 @@ pub fn main() !void {
.file = try app.addFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin")
});
var cwd_realpath_buff: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const cwd_realpath = try std.fs.cwd().realpath(".", &cwd_realpath_buff);
const save_location = try std.fs.path.join(allocator, &.{ cwd_realpath, "project.proj" });
errdefer allocator.free(allocator);
app.project.save_location = save_location;
// try app.appendChannelFromDevice("Dev1/ai0");
// try app.appendChannelFromDevice("Dev3/ao0");
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin");

View File

@ -77,3 +77,9 @@ pub fn initBoundedStringZ(comptime BoundedString: type, text: []const u8) !Bound
pub fn getBoundedStringZ(bounded_array: anytype) [:0]const u8 {
return bounded_array.buffer[0..(bounded_array.len-1) :0];
}
pub inline fn dumpErrorTrace() void {
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
}