feat(console+core): tui, core
This commit is contained in:
78
build.zig
78
build.zig
@@ -1,75 +1,43 @@
|
||||
const std = @import("std");
|
||||
|
||||
// Although this function looks imperative, note that its job is to
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard optimization options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// We will also create a module for our other entry point, 'main.zig'.
|
||||
const exe_mod = b.createModule(.{
|
||||
// `root_source_file` is the Zig "entry point" of the module. If a module
|
||||
// only contains e.g. external object files, you can make this `null`.
|
||||
// In this case the main source file is merely a path, however, in more
|
||||
// complicated build scripts, this could be a generated file.
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
const console_exe_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/console_main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
const console_exe = b.addExecutable(.{
|
||||
.name = "FileTime3",
|
||||
.root_module = console_exe_mod,
|
||||
});
|
||||
|
||||
const vaxis = b.dependency("vaxis", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// This creates another `std.Build.Step.Compile`, but this one builds an executable
|
||||
// rather than a static library.
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "FileTime3",
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
console_exe.root_module.addImport("vaxis", vaxis.module("vaxis"));
|
||||
b.installArtifact(console_exe);
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
// step when running `zig build`).
|
||||
b.installArtifact(exe);
|
||||
const run_console_cmd = b.addRunArtifact(console_exe);
|
||||
run_console_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
// This *creates* a Run step in the build graph, to be executed when another
|
||||
// step is evaluated that depends on it. The next line below will establish
|
||||
// such a dependency.
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
||||
// By making the run step depend on the install step, it will be run from the
|
||||
// installation directory rather than directly from within the cache directory.
|
||||
// This is not necessary, however, if the application depends on other installed
|
||||
// files, this ensures they will be present and in the expected location.
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
// This allows the user to pass arguments to the application in the build
|
||||
// command itself, like this: `zig build run -- arg1 arg2 etc`
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
run_console_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build run`
|
||||
// This will evaluate the `run` step rather than the default, which is "install".
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
const run_console_step = b.step("run", "Run the console app");
|
||||
run_console_step.dependOn(&run_console_cmd.step);
|
||||
|
||||
const exe_unit_tests = b.addTest(.{
|
||||
.root_module = exe_mod,
|
||||
const console_unit_tests = b.addTest(.{
|
||||
.root_module = console_exe_mod,
|
||||
});
|
||||
|
||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||
const run_console_unit_tests = b.addRunArtifact(console_unit_tests);
|
||||
|
||||
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||
// the `zig build --help` menu, providing a way for the user to request
|
||||
// running the unit tests.
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_exe_unit_tests.step);
|
||||
const test_step = b.step("test", "Run unit console tests");
|
||||
test_step.dependOn(&run_console_unit_tests.step);
|
||||
}
|
||||
|
||||
@@ -36,45 +36,11 @@
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||
// // which will prevent zig from using it.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
//
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
.vaxis = .{
|
||||
.url = "git+https://github.com/rockorager/libvaxis.git#ae71b6545c099e73d45df7e5dd7c7a3081839468",
|
||||
.hash = "vaxis-0.1.0-BWNV_JEMCQBskZQsnlzh6GoyHSDgOi41bCoZIB2pW-E7",
|
||||
},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
|
||||
209
src/console/main.zig
Normal file
209
src/console/main.zig
Normal file
@@ -0,0 +1,209 @@
|
||||
const std = @import("std");
|
||||
const vaxis = @import("vaxis");
|
||||
const vxfw = vaxis.vxfw;
|
||||
|
||||
const models = @import("../core/models.zig");
|
||||
const provider = @import("../core/provider/provider.zig");
|
||||
const local_provider = @import("../core/provider/local.zig");
|
||||
const tab = @import("../core/tab/tab.zig");
|
||||
const Tab = tab.Tab;
|
||||
|
||||
/// Our main application state
|
||||
const Model = struct {
|
||||
current_items: vxfw.ListView,
|
||||
tab: *Tab,
|
||||
/// State of the counter
|
||||
count: u32 = 0,
|
||||
/// The button. This widget is stateful and must live between frames
|
||||
button: vxfw.Button,
|
||||
|
||||
/// Helper function to return a vxfw.Widget struct
|
||||
pub fn widget(self: *Model) vxfw.Widget {
|
||||
return .{
|
||||
.userdata = self,
|
||||
.eventHandler = Model.typeErasedEventHandler,
|
||||
.drawFn = Model.typeErasedDrawFn,
|
||||
};
|
||||
}
|
||||
|
||||
/// This function will be called from the vxfw runtime.
|
||||
fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void {
|
||||
const self: *Model = @ptrCast(@alignCast(ptr));
|
||||
switch (event) {
|
||||
// The root widget is always sent an init event as the first event. Users of the
|
||||
// library can also send this event to other widgets they create if they need to do
|
||||
// some initialization.
|
||||
.init => return ctx.requestFocus(self.current_items.widget()),
|
||||
.key_press => |key| {
|
||||
if (key.matches('c', .{ .ctrl = true })) {
|
||||
ctx.quit = true;
|
||||
return;
|
||||
}
|
||||
},
|
||||
// We can request a specific widget gets focus. In this case, we always want to focus
|
||||
// our button. Having focus means that key events will be sent up the widget tree to
|
||||
// the focused widget, and then bubble back down the tree to the root. Users can tell
|
||||
// the runtime the event was handled and the capture or bubble phase will stop
|
||||
.focus_in => return ctx.requestFocus(self.current_items.widget()),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is called from the vxfw runtime. It will be called on a regular interval, and
|
||||
/// only when any event handler has marked the redraw flag in EventContext as true. By
|
||||
/// explicitly requiring setting the redraw flag, vxfw can prevent excessive redraws for events
|
||||
/// which don't change state (ie mouse motion, unhandled key events, etc)
|
||||
fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) std.mem.Allocator.Error!vxfw.Surface {
|
||||
const vm: *Model = @ptrCast(@alignCast(ptr));
|
||||
// The DrawContext is inspired from Flutter. Each widget will receive a minimum and maximum
|
||||
// constraint. The minimum constraint will always be set, even if it is set to 0x0. The
|
||||
// maximum constraint can have null width and/or height - meaning there is no constraint in
|
||||
// that direction and the widget should take up as much space as it needs. By calling size()
|
||||
// on the max, we assert that it has some constrained size. This is *always* the case for
|
||||
// the root widget - the maximum size will always be the size of the terminal screen.
|
||||
const max_size = ctx.max.size();
|
||||
|
||||
// The DrawContext also contains an arena allocator that can be used for each frame. The
|
||||
// lifetime of this allocation is until the next time we draw a frame. This is useful for
|
||||
// temporary allocations such as the one below: we have an integer we want to print as text.
|
||||
// We can safely allocate this with the ctx arena since we only need it for this frame.
|
||||
const count_text = try std.fmt.allocPrint(ctx.arena, "{d}", .{vm.count});
|
||||
const text: vxfw.Text = .{ .text = count_text };
|
||||
|
||||
// Each widget returns a Surface from it's draw function. A Surface contains the rectangular
|
||||
// area of the widget, as well as some information about the surface or widget: can we focus
|
||||
// it? does it handle the mouse?
|
||||
//
|
||||
// It DOES NOT contain the location it should be within it's parent. Only the parent can set
|
||||
// this via a SubSurface. Here, we will return a Surface for the root widget (Model), which
|
||||
// has two SubSurfaces: one for the text and one for the button. A SubSurface is a Surface
|
||||
// with an offset and a z-index - the offset can be negative. This lets a parent draw a
|
||||
// child and place it within itself
|
||||
const text_child: vxfw.SubSurface = .{
|
||||
.origin = .{ .row = 0, .col = 0 },
|
||||
.surface = try text.draw(ctx),
|
||||
};
|
||||
|
||||
const button_child: vxfw.SubSurface = .{
|
||||
.origin = .{ .row = 2, .col = 0 },
|
||||
.surface = try vm.button.draw(ctx.withConstraints(
|
||||
ctx.min,
|
||||
// Here we explicitly set a new maximum size constraint for the Button. A Button will
|
||||
// expand to fill it's area and must have some hard limit in the maximum constraint
|
||||
.{ .width = 16, .height = 3 },
|
||||
)),
|
||||
};
|
||||
|
||||
const list_surface: vxfw.SubSurface = .{
|
||||
.origin = .{ .row = 12, .col = 30 },
|
||||
.surface = try vm.current_items.widget().draw(ctx.withConstraints(ctx.min, .{ .width = 30, .height = 10 })),
|
||||
};
|
||||
|
||||
// We also can use our arena to allocate the slice for our SubSurfaces. This slice only
|
||||
// needs to live until the next frame, making this safe.
|
||||
const children = try ctx.arena.alloc(vxfw.SubSurface, 3);
|
||||
children[0] = text_child;
|
||||
children[1] = button_child;
|
||||
// children[2] = splitView_surface;
|
||||
children[2] = list_surface;
|
||||
|
||||
return .{
|
||||
// A Surface must have a size. Our root widget is the size of the screen
|
||||
.size = max_size,
|
||||
.widget = vm.widget(),
|
||||
// We didn't actually need to draw anything for the root. In this case, we can set
|
||||
// buffer to a zero length slice. If this slice is *not zero length*, the runtime will
|
||||
// assert that it's length is equal to the size.width * size.height.
|
||||
.buffer = &.{},
|
||||
.children = children,
|
||||
};
|
||||
}
|
||||
|
||||
/// The onClick callback for our button. This is also called if we press enter while the button
|
||||
/// has focus
|
||||
fn onClick(maybe_ptr: ?*anyopaque, ctx: *vxfw.EventContext) anyerror!void {
|
||||
const ptr = maybe_ptr orelse return;
|
||||
const self: *Model = @ptrCast(@alignCast(ptr));
|
||||
self.count +|= 1;
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.DebugAllocator(.{}){};
|
||||
const gp_allocator = gpa.allocator();
|
||||
defer {
|
||||
_ = gpa.detectLeaks();
|
||||
_ = gpa.deinit();
|
||||
}
|
||||
|
||||
var tsa = std.heap.ThreadSafeAllocator{ .child_allocator = gp_allocator };
|
||||
const allocator = tsa.allocator();
|
||||
|
||||
var pool: std.Thread.Pool = undefined;
|
||||
try pool.init(.{
|
||||
.allocator = allocator,
|
||||
});
|
||||
defer pool.deinit();
|
||||
|
||||
var localContentProvider = local_provider.LocalContentProvider{ .threadPool = &pool };
|
||||
|
||||
var app = try vxfw.App.init(allocator);
|
||||
defer app.deinit();
|
||||
|
||||
const homeFullName: models.FullName = .{ .path = "/home/adam" };
|
||||
const homeItem = try localContentProvider.getItemByFullName(homeFullName, &.{}, allocator);
|
||||
// defer homeItem.deinit();
|
||||
const c = switch (homeItem.item) {
|
||||
.container => |c| c,
|
||||
.element => unreachable,
|
||||
};
|
||||
|
||||
var tab1 = try allocator.create(Tab);
|
||||
defer allocator.destroy(tab1);
|
||||
|
||||
tab1.init(&pool, allocator);
|
||||
defer tab1.deinit();
|
||||
|
||||
tab1.setCurrentLocation(c);
|
||||
|
||||
// We heap allocate our model because we will require a stable pointer to it in our Button
|
||||
// widget
|
||||
const model = try allocator.create(Model);
|
||||
defer allocator.destroy(model);
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
const arena_allocator = arena.allocator();
|
||||
|
||||
std.Thread.sleep(1 * std.time.ns_per_s);
|
||||
const children1 = if (tab1.currentItems) |items| blk: {
|
||||
const children = try arena_allocator.alloc(vxfw.Widget, items.items.len);
|
||||
for (0.., items.items[0..children.len]) |i, child| {
|
||||
const text1 = try arena_allocator.dupe(u8, child.fullName.path);
|
||||
|
||||
const text_c = try arena_allocator.create(vxfw.Text);
|
||||
text_c.* = vxfw.Text{ .text = text1, .overflow = .clip, .softwrap = false, .style = .{ .bold = true } };
|
||||
children[i] = text_c.widget();
|
||||
}
|
||||
break :blk children;
|
||||
} else &.{};
|
||||
|
||||
const list: vxfw.ListView = .{
|
||||
.children = .{ .slice = children1 },
|
||||
};
|
||||
// Set the initial state of our button
|
||||
model.* = .{
|
||||
.current_items = list,
|
||||
.tab = tab1,
|
||||
.count = 0,
|
||||
.button = .{
|
||||
.label = "Click me!",
|
||||
.onClick = Model.onClick,
|
||||
.userdata = model,
|
||||
},
|
||||
};
|
||||
|
||||
try app.run(model.widget(), .{});
|
||||
std.Thread.sleep(1 * std.time.ns_per_s);
|
||||
}
|
||||
5
src/console_main.zig
Normal file
5
src/console_main.zig
Normal file
@@ -0,0 +1,5 @@
|
||||
const run = @import("console/main.zig").main;
|
||||
|
||||
pub fn main() !void {
|
||||
try run();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Mutex = std.Thread.Mutex;
|
||||
|
||||
const Error = error{
|
||||
Deinitialized,
|
||||
};
|
||||
|
||||
pub const DeinitTracker = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
deinited: bool,
|
||||
usageCount: u16,
|
||||
mutex: Mutex,
|
||||
|
||||
pub fn init(self: *DeinitTracker, allocator: std.mem.Allocator) void {
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.deinited = false,
|
||||
.usageCount = 1,
|
||||
.mutex = Mutex{},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn use(self: *DeinitTracker) Error!void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
|
||||
if (self.deinited) return Error.Deinitialized;
|
||||
|
||||
self.usageCount += 1;
|
||||
}
|
||||
|
||||
pub fn unuse(self: *DeinitTracker) void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
|
||||
self.usageCount -= 1;
|
||||
|
||||
if (self.usageCount == 0) {
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,12 +1,10 @@
|
||||
const std = @import("std");
|
||||
const Provider = @import("provider/provider.zig").Provider;
|
||||
const DeinitTracker = @import("deinit.zig").DeinitTracker;
|
||||
const consts = @import("consts.zig");
|
||||
|
||||
pub const Item = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
provider: Provider,
|
||||
deinitTracker: *DeinitTracker,
|
||||
name: []const u8,
|
||||
displayName: []const u8,
|
||||
fullName: FullName,
|
||||
@@ -20,8 +18,6 @@ pub const Item = struct {
|
||||
self.allocator.free(self.displayName);
|
||||
self.allocator.free(self.fullName.path);
|
||||
self.allocator.free(self.nativePath.path);
|
||||
self.deinitTracker.deinited = true;
|
||||
self.deinitTracker.unuse();
|
||||
for (self.errors.items) |e| {
|
||||
self.allocator.free(e.content);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const models = @import("../models.zig");
|
||||
const Provider = @import("provider.zig").Provider;
|
||||
const ProviderVTable = @import("provider.zig").VTable;
|
||||
const GetItemsError = @import("provider.zig").GetItemsError;
|
||||
const DeinitTracker = @import("../deinit.zig").DeinitTracker;
|
||||
const InitContext = @import("provider.zig").InitContext;
|
||||
|
||||
const FullName = models.FullName;
|
||||
const Item = models.Item;
|
||||
@@ -11,21 +11,14 @@ const ItemEnum = models.ItemEnum;
|
||||
const Element = models.Element;
|
||||
const Container = models.Container;
|
||||
|
||||
fn loadChildren(container: *Container, deinitTracker: *DeinitTracker) void {
|
||||
// TODO: the container might be freed while this runs
|
||||
// Tab should hold something at pass it here
|
||||
fn loadChildren(container: *Container) void {
|
||||
defer {
|
||||
if (!deinitTracker.deinited)
|
||||
container.childrenLoading = false;
|
||||
container.childrenLoading = false;
|
||||
}
|
||||
|
||||
deinitTracker.use() catch {
|
||||
std.debug.print("already deinitialized", .{});
|
||||
return;
|
||||
};
|
||||
defer deinitTracker.unuse();
|
||||
|
||||
var dir = std.fs.cwd().openDir(container.item.nativePath.path, .{ .iterate = true }) catch {
|
||||
if (deinitTracker.deinited) return;
|
||||
|
||||
const errorContent = std.fmt.allocPrint(container.item.allocator, "Could not open directory '{s}'.", .{container.item.nativePath.path}) catch return;
|
||||
container.item.errors.append(.{ .content = errorContent }) catch return;
|
||||
return;
|
||||
@@ -36,7 +29,6 @@ fn loadChildren(container: *Container, deinitTracker: *DeinitTracker) void {
|
||||
while (it.next() catch return) |entry| {
|
||||
const child = container.item.fullName.getChild(entry.name, container.item.allocator) catch return;
|
||||
|
||||
if (deinitTracker.deinited) return;
|
||||
container.children.append(child) catch return;
|
||||
}
|
||||
}
|
||||
@@ -48,11 +40,11 @@ const VTable: ProviderVTable = .{
|
||||
pub fn getItemByFullNameGeneric(
|
||||
context: *anyopaque,
|
||||
fullName: FullName,
|
||||
initContext: *const InitContext,
|
||||
allocator: std.mem.Allocator,
|
||||
globalAllocator: std.mem.Allocator,
|
||||
) GetItemsError!Item {
|
||||
) GetItemsError!*Item {
|
||||
const self: *LocalContentProvider = @ptrCast(@alignCast(context));
|
||||
return self.getItemByFullName(fullName, allocator, globalAllocator);
|
||||
return self.getItemByFullName(fullName, initContext, allocator);
|
||||
}
|
||||
|
||||
pub const LocalContentProvider = struct {
|
||||
@@ -61,9 +53,9 @@ pub const LocalContentProvider = struct {
|
||||
pub fn getItemByFullName(
|
||||
self: *LocalContentProvider,
|
||||
fullName: FullName,
|
||||
initContext: *const InitContext,
|
||||
allocator: std.mem.Allocator,
|
||||
globalAllocator: std.mem.Allocator,
|
||||
) GetItemsError!Item {
|
||||
) GetItemsError!*Item {
|
||||
const stat = std.fs.cwd().statFile(fullName.path) catch return GetItemsError.NotExists;
|
||||
|
||||
return switch (stat.kind) {
|
||||
@@ -84,12 +76,15 @@ pub const LocalContentProvider = struct {
|
||||
val,
|
||||
fullName,
|
||||
allocator,
|
||||
globalAllocator,
|
||||
);
|
||||
|
||||
try self.threadPool.spawn(loadChildren, .{ container, container.item.deinitTracker });
|
||||
if (!initContext.skipChildInit) {
|
||||
try self.threadPool.spawn(loadChildren, .{container});
|
||||
} else {
|
||||
container.childrenLoading = false;
|
||||
}
|
||||
|
||||
break :blk container.item;
|
||||
break :blk &container.item;
|
||||
},
|
||||
.file => blk: {
|
||||
const element = try allocator.create(Element);
|
||||
@@ -107,10 +102,9 @@ pub const LocalContentProvider = struct {
|
||||
val,
|
||||
fullName,
|
||||
allocator,
|
||||
globalAllocator,
|
||||
);
|
||||
|
||||
break :blk element.item;
|
||||
break :blk &element.item;
|
||||
},
|
||||
else => @panic(
|
||||
"Unsupported file type\n",
|
||||
@@ -124,29 +118,17 @@ pub const LocalContentProvider = struct {
|
||||
innerItem: ItemEnum,
|
||||
fullName: FullName,
|
||||
allocator: std.mem.Allocator,
|
||||
globalAllocator: std.mem.Allocator,
|
||||
) !void {
|
||||
var deinitTracker = try globalAllocator.create(DeinitTracker);
|
||||
deinitTracker.init(globalAllocator);
|
||||
|
||||
const basename = std.fs.path.basename(fullName.path);
|
||||
|
||||
const name = try allocator.alloc(u8, basename.len);
|
||||
@memcpy(name, basename);
|
||||
|
||||
const displayName = try allocator.alloc(u8, basename.len);
|
||||
@memcpy(displayName, basename);
|
||||
|
||||
const fullName2 = try allocator.alloc(u8, fullName.path.len);
|
||||
@memcpy(fullName2, fullName.path);
|
||||
|
||||
const nativePath = try allocator.alloc(u8, fullName.path.len);
|
||||
@memcpy(nativePath, fullName.path);
|
||||
const name = try allocator.dupe(u8, basename);
|
||||
const displayName = try allocator.dupe(u8, basename);
|
||||
const fullName2 = try allocator.dupe(u8, fullName.path);
|
||||
const nativePath = try allocator.dupe(u8, fullName.path);
|
||||
|
||||
item.* = Item{
|
||||
.allocator = allocator,
|
||||
.provider = contentProvider.provider(),
|
||||
.deinitTracker = deinitTracker,
|
||||
.name = name,
|
||||
.displayName = displayName,
|
||||
.fullName = .{ .path = fullName2 },
|
||||
|
||||
@@ -2,9 +2,9 @@ pub const VTable = struct {
|
||||
getItemByFullName: *const fn (
|
||||
self: *anyopaque,
|
||||
fullName: FullName,
|
||||
initContext: *const InitContext,
|
||||
allocator: std.mem.Allocator,
|
||||
globalAllocator: std.mem.Allocator,
|
||||
) GetItemsError!Item,
|
||||
) GetItemsError!*Item,
|
||||
};
|
||||
|
||||
pub const GetItemsError = error{
|
||||
@@ -19,13 +19,17 @@ pub const Provider = struct {
|
||||
pub inline fn getItemByFullName(
|
||||
self: *const Provider,
|
||||
fullName: FullName,
|
||||
initContext: *const InitContext,
|
||||
allocator: std.mem.Allocator,
|
||||
globalAllocator: std.mem.Allocator,
|
||||
) GetItemsError!Item {
|
||||
return &self.vtable.getItemByFullName(self.object, fullName, allocator, globalAllocator);
|
||||
) GetItemsError!*Item {
|
||||
return self.vtable.getItemByFullName(self.object, fullName, initContext, allocator);
|
||||
}
|
||||
};
|
||||
|
||||
pub const InitContext = struct {
|
||||
skipChildInit: bool = false,
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const models = @import("../models.zig");
|
||||
const Item = models.Item;
|
||||
|
||||
@@ -9,7 +9,8 @@ const Item = models.Item;
|
||||
pub const Tab = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
currentLocation: ?*Container,
|
||||
currentItems: ?std.ArrayList(Item),
|
||||
currentItems: ?std.ArrayList(*Item),
|
||||
currentItemsChanged: bool = false,
|
||||
threadPool: *std.Thread.Pool,
|
||||
_private: Private,
|
||||
|
||||
@@ -41,17 +42,46 @@ pub const Tab = struct {
|
||||
self.currentLocation = newLocation;
|
||||
|
||||
//TODO: Proper error handling
|
||||
std.Thread.Pool.spawn(self.threadPool, loadItems, .{ self, newLocation }) catch unreachable;
|
||||
std.Thread.Pool.spawn(self.threadPool, loadItemsWrapper, .{ self, newLocation }) catch unreachable;
|
||||
}
|
||||
|
||||
fn loadItems(self: *Tab, location: *Container) void {
|
||||
fn loadItemsWrapper(self: *Tab, location: *Container) void {
|
||||
loadItems(self, location) catch return;
|
||||
}
|
||||
fn loadItems(self: *Tab, location: *Container) !void {
|
||||
if (self._private.currentItemsAllocator) |arena| {
|
||||
arena.deinit();
|
||||
}
|
||||
if (self.currentItems) |items| {
|
||||
items.deinit();
|
||||
}
|
||||
|
||||
self._private.currentItemsAllocator = std.heap.ArenaAllocator.init(self.allocator);
|
||||
const arenaAllocator = &self._private.currentItemsAllocator.?;
|
||||
const arena = arenaAllocator.allocator();
|
||||
|
||||
var threadSafeAllocator = std.heap.ThreadSafeAllocator{.child_allocator = arena};
|
||||
const allocator = threadSafeAllocator.allocator();
|
||||
|
||||
errdefer {
|
||||
arenaAllocator.deinit();
|
||||
self._private.currentItemsAllocator = null;
|
||||
}
|
||||
|
||||
self.currentItems = std.ArrayList(*Item).init(allocator);
|
||||
errdefer {
|
||||
self.currentItems.?.deinit();
|
||||
self.currentItems = null;
|
||||
}
|
||||
|
||||
while (location.childrenLoading) {
|
||||
std.Thread.sleep(1 * std.time.ns_per_ms);
|
||||
}
|
||||
|
||||
for (location.children.items) |item| {
|
||||
_ = item;
|
||||
const resolvedItem = try location.item.provider.getItemByFullName(item, &.{ .skipChildInit = false }, allocator);
|
||||
try self.currentItems.?.append(resolvedItem);
|
||||
self.currentItemsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +92,5 @@ pub const Tab = struct {
|
||||
if (self._private.currentItemsAllocator) |arena| {
|
||||
arena.deinit();
|
||||
}
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user