Odysseus uses GLib’s GError to report syntax errors for it’s templates before rendering it via a different template (which yes, means I need to error handle my error handling).
It’s a simple C struct with fields for “domain”, error code, and a textual message. Domain is the most interesting of those as it’s a “quark”, that is a string stored in a global hashmap for deduplication and cheap equality tests.
The Vala compiler wraps this with your traditional try/catch syntax, with some help from g_error_matches(), and g_error_propagate() which copies errors into the expected pointer.
That was trivial! So I’ll cover another GLib (or rather GIO) structure with Vala syntactic sugar that Odysseus benefits highly from in implementing it’s templating language.
I want to make sure loading an internal page cannot freeze your browser, so I use “async methods” to frequently hand control back to the mainloop where other events can be handled.
P.S. At “Prosody” runtime errors are silenced via it’s “Empty” type, this is generally the best I can do.
The most interesting aspect of async methods is entirely in the Vala compiler. There it compiles the async methods to:
- Store all it’s data in a dedicated object compiled specifically for it. These are collected upon compiling expression blocks, in the same logic gathering lambda closures.
- Rewrite method calls prefixed with yields to take a callback specifically compiled for this method, update a state field, return false, and add a label to return to.
- compile a switch statement to jump to those labels, revisited after the method has otherwise compiled when we know how many branches it needs.
- wrap that function with others and GTask (and it’s GAsync superclass) to manage state, mainloops, and errors whilst creating more convenient function signatures.
But it’s core, async methods can call other async methods and return to wait until it’s called back by the callee at which point the opening switch statement jumps back to the current point.
The GTask for these async methods, which wraps the callback passed into the start method, contains a variaty of data including:
- a “name” to be associated with any mainloop “sources” for this operation
- a “priority” for any of those sources
- the “task data” to pass to the wrapped async method, and a destructor
- the mainloop “context” to which it should return, and to which it should attach any sources
- a creation time, to determine whether to wait until the next mainloop iteration
- an atomic “cancellable” flag for callers to set to stop the async method from returning.
- the callback for when this method completes, and data to pass to it
- bits to track it’s current state (much like JS promises), and control things whether it checks the cancellable. Some of these are mutext protected.
- the returned error and data.
Or it can use these fields to control threads used to make synchronous operations async.
But to summarise to “return” a GTask:
- Updates it’s state
- Check the current mainloop context and iteration to determine whether to return immediately wait until the next “idle” event from the mainloop.
- Push the previous context back onto the mainloop stack.
- Run the callback passing itself (as type GAsyncResult) and it’s associated data, marking that as done.
- Notify anyone else of the completion, before restoring the mainloop stack.