Rewrote the cache again. This time I found The Right Way to do it and it was remarkably painless. Now I have a single cache server that serializes all cache access and does all cache updates, and a bunch of traversal threads that send information to the cache server. Previously I had OO-modeled it badly with the cache entries passing on information updates to their parents and whatnot. Which was horrendously complex to make threadsafe, very difficult to understand and even more difficult to make work properly with traversal cancellations and cache invalidations. With the new way, I treat everything inside the cache server as single-threaded code and that makes things easy.
I also managed to make the cache invalidation more fine-grained from the previous "What? A file has changed?! Sound the klaxons, HÄLÄRM HÄLÄRM!!! DUMP THE CACHE, EMERGENCY DIVE!! LET'S REDO EVERYTHING!" Which was not too much fun with 1.2 megafile directory hierarchies. Now the cache tries to modify only those entries that were affected by the change, though that means that I get to deal with fun bugs :|
Last third of the day I humbly intend to spend on *drumroll* documentation! And cheers were heard all around!
Here's the rectangle helper for the heck of it. Been too long since I pasted random bits of code here anyhow.
public static void DrawRectangle
(Context cr, double x, double y, double w, double h, Rectangle target)
double x_a = cr.Matrix.X0+x*cr.Matrix.Xx;
double y_a = cr.Matrix.Y0+y*cr.Matrix.Yy;
double w_a = cr.Matrix.Xx*w;
double h_a = cr.Matrix.Yy*h;
double y2_a = y_a + h_a;
double x2_a = x_a + w_a;
x_a = Math.Max(-1, Math.Min(target.X+target.Width+1, x_a));
x2_a = Math.Max(-1, Math.Min(target.X+target.Width+1, x2_a));
y_a = Math.Max(-1, Math.Min(target.Y+target.Height+1, y_a));
y2_a = Math.Max(-1, Math.Min(target.Y+target.Height+1, y2_a));
w_a = x2_a - x_a;
h_a = y2_a - y_a;
cr.Rectangle (x_a, y_a, w_a, h_a);