Releases: zensical/zrx
0.0.22
Summary
This version clarifies the concept of a Scope, which represents the context of a stream function execution, and adds support for diagnostics to it. Stream functions can now report diagnostics that can be consumed by the runtime and rendered in the CLI, as well as other potential consumers. Moreover, stream function implementations have been updated and made much more flexible by expecting the return of any value that implements IntoResult. This makes it easier to implement infallible stream functions, as well as to use the ? operator with those that are fallible.
Please note that this version includes fundamental breaking changes to the API, which were necessary in order to give module authors maximum flexibility, yet abstracting away the complexity of diagnostics and error handling.
Changelog
Breaking changes
- 5a90a74 zrx-scheduler, zrx-stream – add
Diagnosticsupport toScope - 85e4507 zrx-stream – change
FlatMapFnto take mutable reference toScope - 165c600 zrx-stream – change
FilterFnto take mutable reference toScope - f78174a zrx-stream – change
InspectFnto take mutable reference toScope - ef584b9 zrx-stream – change
GetFnto take mutable reference toScope - 656941f zrx-stream – change
DefaultFnto take mutable reference toScope - e3e983e zrx-stream – change
FilterMapFnto take mutable reference toScope - b1fd9fb zrx-diagnostic – remove
Reporttype and impls - 1f84d32 zrx-scheduler, zrx-stream – remove
Derefimpl forScope - e78d244 zrx-stream – change
MapFnimpls to returnIntoResult - a1050a5 zrx-stream – change
MapFnto take mutable reference toScope - a2d5bd5 zrx-scheduler, zrx-stream – improve flexibility of
Keymethod impls - cefdbc2 zrx-scheduler, zrx-stream – rename
ScopetoKeyandScopedtoScope
Features
- fc4165a zrx-stream – switch
tracingto useScope::keyin stream functions - 331d81e zrx-scheduler – add
IntoResultconversion trait for stepResult - 1625f36 zrx-scheduler – add
Scope::keyimpl - 2e88a17 zrx-scheduler – add
IntoErrorconversion trait and impls for stepError - 204d582 zrx-graph – add
Graph::groupsto find nodes per key function - 11ff68a zrx-graph – allow references in
Graph::group_sinksandGraph::group_sources - 8439ace zrx-graph – add
Graph::group_sourcesto find sources per key function - c66b29b zrx-graph – add
Graph::group_sinksto find sinks per key function - 366d7e9 zrx-graph – add
Graph::is_acyclicimpl (Kahn's algorithm) - 30fd302 zrx-id – add
Cloneimpl forMatcher
Bug fixes
- 7798ee3 zrx-scheduler – mitigate cycles in inter-module subscriptions
Refactorings
0.0.21
Summary
This version fixes several bugs that caused barriers to not be fulfilled correctly, which surfaced during the integration of the new runtime into Zensical, including a race condition in the WorkSharing strategy of the Executor.
Changelog
Features
- ce29f93 zrx-stream – switch
Barriers::insertto acceptInto<Scope<I>> - ddcaba2 zrx-stream – add
Barriers::lenandBarriers::is_emptyimpls - 161e35d zrx-stream – add
Debugimpl forAdvance - 4cfa644 zrx-stream – add
Stream::inspectoperator
Bug fixes
- c860754 zrx-stream – contained
Scopeinstances inBarrierreset on everyEvent - 538d026 zrx-stream – early return from
Barries::notifyon existingScope - 6fa7285 zrx-stream – add values to emitted items of
Stream::select - d3f2e56 zrx-stream – unnecessary
must_useattributes onStreamoperator functions - 35a7178 zrx-scheduler –
Actionsdependency graph can contain self-loops - ad6b3ad zrx-executor – race condition in
WorkSharingstrategy pending task computation
Refactorings
- 664504d zrx-scheduler – queue
Eventsubmission and drain onSchedule::submit
0.0.20
Summary
This version adds an efficient Barrier implementation to zrx-stream, which is a building block for complex operators that allow to perform fine-grained synchronization between streams, like Stream::select. Additionally, function traits for use in Stream operators have been refactored to be more flexible and easier to use, removing the need for newtype wrappers by leveraging specialized trait implementations for common function signatures.
Highlights
- Add
Barrierimplementation tozrx-streamfor efficient synchronization - Allow an
Actionto expressInterestfor certainEventtypes - Remove
Arcfromzrx-id, sinceScopealready uses anArcinternally - Fix race condition where a
Frontiercould be terminated prematurely
Changelog
Breaking changes
- 47f3f65 zrx-stream – increase flexibility of operator functions
Features
- 2444eb2 zrx-stream – add
Stream::selectoperator impl - e2f9337 zrx-stream – add
Stream::productoperator impl - 501713e zrx-stream – add
BarrierandBarriersimpls - dbb8c6f zrx-scheduler – allow
Actionto registerInterestforScopelifecycle - 1bff0be zrx-stream – add
KeyFntrait and impls for common signatures - ae48b18 zrx-stream – add
FlatMapFntrait and impls for common signatures - 05fba90 zrx-stream – add
DefaultFntrait and impls for common signatures - 606184d zrx-id – add
Valueimpl forId - 3e5574f zrx-scheduler – add
Errorconversions forScope - ba5a668 zrx-scheduler – add
Scope::try_as_idfor use in new function shapes
Bug fixes
- f454d16 zrx-scheduler –
Frontierterminated prematurely, leading to race condition
Refactorings
0.0.19
Summary
This version ships an extensive refactoring of zrx-store, which is fundamental for efficient and optimal barrier management. The most notable breaking change is the removal of the StoreMut::insert_if_changed method and all of its implementations, which has been merged with StoreMut::insert to only return the prior value if it changed, simplifying the API and improving performance.
Stash has been optimized to iterate over its underlying Slab for optimal performance, and the Items::insert and Items::remove methods now both return whether the operation resulted in a change. The logic of Matches::has_any and Matches::has_all has been inverted for clarity, and Matches::insert has been renamed to Matches::add to better reflect its functionality.
Changelog
Breaking changes
- 13ffd28 zrx-store – remove
StoreMut::insert_if_changed, merge withStoreMut::insert - f26f1d4 zrx-store – change
Stashto iterate overSlabfor performance - a74a05b zrx-store – add return flag to
Items::insertandItems::remove - 884a75a zrx-id – invert logic of
Matches::has_anyandMatches::has_allfor clarity - 0f3f350 zrx-id – rename
Matches::inserttoMatches::add
Features
- 36b05ff zrx-store – add
Drainiterator impl forItems - 4aa08a3 zrx-store – derive
DebugforIntoIterimpl forItems - 04a5ace zrx-store – derive
CloneforStash - 5c4e93f zrx-store – derive
DebugforIterimpls forStoreimpls - e952fa0 zrx-id – derive
DebugforIntoIterimpl forMatches - 3fa4432 zrx-store – add optimized
StoreMut::insert_if_changedimpl forSlab - d3c921c zrx-store – add
Stash::keyimpl for index-to-key lookup - a10c59a zrx-store – add
Stash::slotsandStash::slots_mutiterator impls - bfbf831 zrx-store – add non-consuming
Iterimpl forItems - f0d79a6 zrx-store – add
IntoIteratorimpl for mutable reference toStash - 9dc2b85 zrx-store – add
IntoIteratorimpl for mutable reference toQueue
Bug fixes
- 61cf1be zrx-store –
Stash::index_mutcan't return key, switch to value-only
Refactorings
0.0.18
Summary
This version introduces a Stash data structure, which is fundamentally a Slab with key-to-index mapping. It's essential for implementing efficient barrier and frontier management, allowing for quick insertions and removals. The Stash is designed to be a versatile building block for various use cases, particularly those requiring temporal storage. Moreover, missing iterator implementations for some Store implementors have been added.
Changelog
Breaking changes
- ffb60c9 zrx-store – switch
Stashto store(K, V)for key-to-index mapping - ddb1774 zrx-store – remove superseded
StoreDeltatrait
Features
- 676a6b6 zrx-store – add
StoreRangeimpl forStashfor range iteration - 0b30af7 zrx-store – add
Stash::getandStash::removemethod impls - b192ce1 zrx-store – add
Itemsimpl to manage sets ofStashitems - 73303a7 zrx-scheduler, zrx-store – add top-level export for
QueueandStash - 847273f zrx-store – add
Stashstore impls for temporary storage - da481ea zrx-store – add
ExactSizeIteratorimpls forOrderediterators - 4dbcfa8 zrx-store – add
ExactSizeIteratorimpls forSlabiterator adapters - 6c8fc69 zrx-store – add
IntoIteratorimpl forQueue - 4b5cec7 zrx-storage – add
Storages::newimpl for consistency
Refactorings
- fd4de90 zrx-executor, zrx-id, zrx-scheduler, zrx-storage – rename files containing set impls to
set.rs
0.0.17
Summary
This version ships the initial version of the module system and includes a ground up rewrite of zrx-scheduler and zrx-stream to support it. Together with the fundamental changes made to zrx-id, zrx-storage and zrx-graph in prior versions, this is a major milestone in the development of Zensical and the foundation for future work on the system.
Note
Please note that this version does not yet include a public module API. The module system is still in its early stages and its API is subject to change. With the initial release of the module system, we've completed phase 2 of our phased transition strategy, and are entering phase 3 in which we will be porting functionality from MkDocs plugins to Zensical modules.
Module system
The initial version of the module system ships with this release, and represents the conceptual core of Zensical's long-term architecture. The fundamental premise is straightforward: all specific functionality is provided as modules, resulting in a system that is inherently flexible and composable. Users can pick and choose modules according to their requirements, with no unnecessary overhead from functionality they don't use.
Example
How module implementations look in practice:
use zrx::id::selector;
use zrx::module::{Module, Context, Result};
/// Optimize module.
pub struct Optimize {
optimize_png: bool,
}
impl Module for Optimize {
/// Initializes the optimize module.
fn setup(&self, ctx: &mut Context) -> Result {
if self.optimize_png {
let stream = ctx.add_stream(selector!(location = "**/*.png"));
stream.map(|path| optimize_png(path));
}
}
}A few things worth noting about this example:
-
Business logic stays pure. The
optimize_pngfunction doesn't know anything about streams, scheduling, or other modules – it just takes a path and does its job. The module system handles all the plumbing. This makes it much easier to test and maintain business logic, and also makes it reusable outside of Zensical if needed. -
Configuration is just a struct. Module behavior is controlled through plain fields, with no registration callbacks or lifecycle hooks to implement. This makes it easy to understand and modify module behavior without diving into complex initialization logic. All interaction with the runtime is through the context object, which provides a stable and evolvable API.
-
Conditional setup is trivial. Entire parts of the workflow can be enabled or disabled at initialization time, without any special support from the system. When
optimize_pngis false, the module simply doesn't add any streams to the workflow, and the scheduler won't execute any of its logic. -
The API surface is minimal. Implementing a module means implementing a single method,
setup. Everything else follows from the stream and selector abstractions. This makes the API easy to learn and reason about, and also makes it easier for us to evolve it without breaking existing modules.
Architecture
The module system is tightly integrated with the zrx-scheduler, zrx-stream and zrx-id crates. Each module is built from a dedicated workflow, representing a set of stream transformations. Streams can be combined and transformed using operators like joins and maps, a concept borrowed from reactive programming.
Modules can be attached and detached at runtime. The scheduler automatically recomputes the execution plan whenever modules are added or removed, so users can freely add and remove functionality without manual intervention.
Modules can cooperate through typed subscriptions. A module can subscribe to streams from other modules, filtered by type – this is how modules communicate and share work, without shared state or brittle callbacks. Each module runs in isolation from other modules, yet can subscribe to them. Communication is implemented with channels.
Scheduler
The scheduler has been rewritten from the ground up to provide a more flexible and powerful execution model.
Performance
Several fundamental changes were made to improve performance:
-
Batch processing. Actions - the work units the scheduler executes, with each stream operator compiled into one - now process items in batches rather than individually, reducing per-item overhead significantly.
-
Type erasure at the data structure level. Previously, type erasure was performed on a value level, requiring every value to be individually boxed. The new scheduler erases types at the data structure level instead, improving cache coherence and locality. This applies to inter-module channels as well, where the channels themselves are boxed rather than the values they carry.
-
Strategic downcasting. Downcasts are now placed so that the number of downcasts per action execution is constant, rather than linear in the number of inputs or outputs.
Architecture
Each action now has a dedicated store it can manage. Different store shapes are supported - unordered, ordered, and indexed - which is essential for certain types of operators and lays the groundwork for transparent caching, planned for a future release.
Currently, module execution happens on the main thread, with parallelizable work in operators offloaded to a thread pool. Because inter-module communication is implemented with channels and modules are fully self-contained, the architecture is already structured to support offloading modules onto their own threads - a further performance improvement we plan to pursue.
This only scratches the surface of the changes that went into this rewrite, which represents roughly three months of work. The new design is still an early version and will continue to evolve, but the fundamental architecture is in place. We're confident it strikes close to the right balance between performance, ease of use and flexibility.
What's next
We understand the interest in the module system and are working hard to make it available as soon as possible. Yet we want to be confident that the API is stable and well-designed before we release it, so we are taking the time to iterate on it internally before making it public. We will be sharing more details about the module system and its API in the coming weeks, and we are excited to see what the community builds with it once it's available.
Early previews of the module API will be available through Zensical Spark.
Changelog
Breaking changes
- fab1c11 zrx-scheduler, zrx-stream – redesign architecture for performance and modularity
- 75af4dd zrx-graph – remove
Graph::to_edge_graph(deprecated in 0.0.6) - 6179105 zrx-id – move
selectormodule out ofmatcherone level up - a8e422e zrx-id – move
expressionmodule out offilterone level up
Features
- ff5cab6 zrx, zrx-module – add
Moduletrait and related impls
0.0.16
Summary
This version introduces zrx-storage, a crate that extends zrx-store with a higher-level API for managing collections of data with synchronization between multiple stores. It's intended for storing intermediate state in the scheduler and executor, and for sharing data between different modules and workflows. Moreover, it's the foundation to implement action-level caching. A set of accessors for join- and set-operations on stores is provided as well.
Additionally, several improvements and bug fixes have been made to the zrx-id crate, including better handling of specificity and ordering of expressions.
Highlights
- The
StorageandStoragestypes allow access of multiple stores at once. - The
Specifiedtype addsSpecificityordering to arbitrary values. - The
Executorcan now be shared between threads.
Changelog
Breaking changes
- 42e3c02 zrx-store – change
Collection::downcast_ref/muttarget toCollection
Features
- 1348e83 zrx, zrx-storage – add
StorageandStoragestypes for data store synchronization - 9e5af24 zrx-executor – switch
StrategyinExecutortoArcto share between threads - 11b62f1 zrx-id – add
Defaultimpl forSpecified - ba22db9 zrx-id – add
Defaultimpl forExpression - 499d66b zrx-id – add
PartialEqandEqimpls toExpressionandOperand - a817004 zrx-id – add
Specifiedfor ordering values bySpecificity
Bug fixes
- e092ce4 zrx-id – always order all-zero
Specificityfirst - 1fd37da zrx-id –
Specificityis consumed bySpecified::specificityaccessor
Refactorings
0.0.15
Summary
This version introduces Specificity — an ordering and tie-breaking concept for selectors and expressions that allows our runtime to determine which module wins when multiple modules compete for a resource. The idea is borrowed from CSS, where more specific selectors take precedence, and adapted here for globs, selectors, and expressions in ZRX.
Background
ZRX uses selectors to query and subscribe to resources. Selectors can be combined into expressions using ANY, ALL, and NOT operators. Each selector has 6 components, each of which can be a glob pattern:
zrs:<provider>:<resource>:<variant>:<context>:<location>:<fragment>
How Specificity works
Each component contributes to a 4-tuple (A, B, C, L):
- A — literal segments (most specific)
- B — single-wildcard segments (
*,?) - C — double-wildcard segments (
**) - L — literal character count (tiebreaker)
Tuples are compared lexicographically — higher is more specific. Expressions combine specificities according to their operator: ALL sums them (constraints stack), ANY takes the minimum (only as specific as the broadest arm), and NOT contributes nothing (it filters, but doesn't select).
Examples
zrs:{git,file}:::{docs}:index.md: # (3, 0, 0, 15)
zrs::::docs:{index,about}.md: # (2, 0, 0, 12)
zrs:::::index.{md,rst}: # (1, 0, 0, 8)
zrs:::::{*}: # (0, 1, 0, 0)This is an early alpha — documentation on using specificity in the router and other modules will follow. This release includes the core implementation for selectors and expressions.
Changelog
Breaking changes
- a69615d zrx-graph – replace
Graph::withwithGraph::adjacentaccessor
Features
- b451d42 zrx-id – add
ToSpecificityimpl forTermandExpression - a3ea5eb zrx-id – add
ToSpecificityimpl forIdandSelector - 38b3ccd zrx-id – add
AsRefimpl toIdandSelectorto obtainFormat - a4b1fd3 zrx-id – add
Specificityimpl for computing glob ordering - dd4e3f9 zrx-graph – add
Traversal::initialimpl to obtain initial nodes
Bug fixes
- 9e6eaa2 zrx-id – computed
SpecificityforExpression::anynot aligned - 41b3e49 zrx-id – comparison of
IdandSelectorsusceptible to hash collisions
Performance improvements
- dad5ed4 zrx-id – provide dedicated
PartialEqimpl forSelector - b2870f5 zrx-id – remove branching in
PartialEqimpl forId
Refactorings
- c6fd98e zrx-id – change
Specificitycomputation to non-consuming
0.0.14
Summary
This version includes several improvements to the graph traversal logic, including convergence of traversals, which allows to combine multiple traversals into a single one, as well as several refactorings to improve code clarity and maintainability. Combining traversals is essential for multi-source modules, where traversals are created for each source and then combined and synchronized for execution.
Changelog
Breaking changes
- 6e2a178 zrx-graph – move
Graph::emptyimpl intoGraph::default - 2b23a19 zrx-id – replace
IntoExpressionwithInto<Expression> - 53cbd20 zrx-executor, zrx-scheduler – make
Executor::capacityandStrategy::capacitynon-optional - 12e626f zrx-id – remove
Builder::set_*APIs in favor of consuming methods - 0fb0e31 zrx-graph – rename
visitormodule intoiter
Features
- 8350ec7 zrx-graph – add
Traversal::convergeimpl - b529775 zrx-graph – add
Eqimpl forTopology - fbb9130 zrx-graph – add
AsRefimpl forGraph - a84bf3f zrx-graph – add
Graph::withandGraph::with_mutoperator impls
Bug fixes
- 3679f97 zrx-graph – duplicate initial nodes break
Traversal::converge
Performance improvements
- 7f13011 zrx-graph – replace
BTreeSetwithHashSetin convergence computation
Refactorings
0.0.13
Summary
This version fixes a race condition in the computation of running tasks in the executor, which could lead to premature termination of the execution loop. Additionally, it includes various improvements and refactorings across the codebase, such as renaming traits for better clarity, adding missing methods, and optimizing hash computations.
Changelog
Breaking changes
- fb3c836 zrx-id – rename non-consuming
TryInto*traits intoTryTo*
Features
- 099a9fb zrx-graph – add
IntoIteratorimpl for graphBuilder - 55e9740 zrx-store – add missing
StoreMutmethods toCollection
Bug fixes
- ac6f0d5 zrx-executor – race condition in task count computation in both strategies
- 2623a30 zrx-id – missing prefix for
Defaultimpls of id and selectorBuilder
Performance improvements
- 0915c4a zrx-id – use
ahashfor faster precomputing of hashes
Refactorings
- f1c28a7 zrx-executor – simplify
Immediateconstruction methods - 229fee6 zrx-graph – move
Builderinstantiation toDefault - d702beb zrx-id, zrx-store – remove elided lifetime on
Formatterargument - 96d4e9a zrx-store – switch
Orderedto useBTreeSetfor value ordering - 08c8593 zrx-id – expose
OperandandOperator, as they're part of the public API