-
Pl
chevron_right
Christian Hergert: Directory Listings with Foundry
news.movim.eu / PlanetGnome • 21 September • 3 minutes
I took a different approach to directory listings in Foundry. They use
GListModel
as the interface but behind the scenes it is implemented with futures and and fibers.
A primary use case for a directory listing is the project tree of an IDE. Since we use
GtkListView
for efficient trees in GTK it we expose a
GListModel
. Each item in the directory is represented as a
FoundryDirectoryItem
which acts just a bit like a specialized
GFileInfo
. It contains information about all the attributes requested in the directory listing.
You can also request some other information that is not traditionally available via
GFileInfo
. You can request attributes that will be populated by the version control system such as if the file is modified or should be ignored.
Use a
GtkFilterListModel
to look at specific properties or attributes. Sort them quickly using
GtkSortListModel
. All of this makes implementing a project tree browser very straight forward.
Reining in the Complexity
One of the most complex things in writing a directory listing is managing updates to the directory. To manage some level of correctness here Foundry does it with a fiber in the following ordering:
- Start a file monitor on the directory and start queing up changes
- Enumerate children in the directory and add to internal list
- Start processing monitor events, starting with the backlog
Performance
There are a couple tricks to balancing the performance of new items being added and items being removed. We want both to be similarly well performing.
To do this, new items are always placed at the end of the list. We don’t care about sorting here because that will be dealt with at a higher layer (in the
GtkSortListModel
). That keeps adding new items quite fast because we can quickly access the end of the list. This saves us a lot of time sorting the tree just for the removal.
But if you aren’t sorting the items, how do you make removals quick? Doesn’t that become
O(n)
?
Well not quite. If we keep a secondary index (in this case a simple
GHashTable
) then we can store a key (the file’s name) which points to a stable pointer (a
GSequenceIter
). That lookup is
O(1)
and the removal from a
GSequence
on average are
O(log n)
.
Big O notation is often meaningless when you’re talking about different systems. So let’s be real for a moment. Your callback from
GListModel::items-changed()
can have a huge impact on performance compared to the internal data structures here.
Reference Counting and Fibers
When doing this with a fiber attention must be taken to avoid over referencing the object or you risk never disposing/finalizing.
One way out of that mess is to use a
GWeakRef
and only request the object when it is truly necessary. That way, you can make your fiber cancel when the directory listing is disposed. In turn cleanup your monitor and other resources are automatically cleaned up.
I do this by creating a future that will resolve when there is more work to be done. Then I release my
self
object followed by awaiting the future. At that point I’ll either resolve or reject from an error (including the fiber being cancelled).
If I need
self
again because I got an event, it’s just a
g_weak_ref_get()
away.
Future-based File Monitors
I’m not a huge fan of “signal based” APIs like
GFileMonitor
so internally
FoundryFileMonitor
does something a bit different. It still uses
GFileMonitor
internally for outstanding portability, but the exposed API uses
DexFuture
.
This allows you to call when you want to get the next event (which may already be queued). Call
foundry_file_monotor_next()
to get the next event and the future will resolve when an event has been received. This makes more complex awaiting operations feasible too (such as monitoring multiple directories for the next change).
foundry-directory-listing.c , foundry-directory-item.c , and foundry-file-monitor.c .