Today we are excited to announce the release of Chronograph 1.0.0. Chronograph is an open-source reactive computational engine, implemented in TypeScript. It serves as a critical building block of our Bryntum Gantt housing all the business and scheduling logic.

Reactive computations became popular recently, mainly through the React, Vue and Angular libraries and there are plenty of existing libraries for this purpose. However, Chronograph introduces some novel and unique properties:

Cancelable transactions

O(1) undo/redo

Lazy/eager, sync/async computations

Data branching

Mixed computational unit (user input/calculated value)

Unlimited stack depth

Disciplined approach to cyclic computations

Entity/Relation framework

As the name suggests, Chronograph uses a directed acyclic graph for data representation:



Hello world

class Person extends Entity.mix(Base) { @field() firstName : string @field() lastName : string @field() fullName : string @calculate('fullName') calculateFullName () : string { return this.firstName + ' ' + this.lastName } } const person = Person.new({ firstName : 'Mark', lastName : 'Twain' }) const replica = Replica.new() replica.addEntity(markTwain) console.log(person.fullName) // "Mark Twain"

History

Chronograph started as a research project when we started rewriting the business logic layer of our Ext Gantt product. It took several iterations before the current 1.0.0 version.

The first attempt was purely functional and included experimenting with the simplex method. It was quite semantic, but the performance was not satisfactory. We then migrated to a static reactive graph, with formulas declaring dependencies upfront. This gave us a huge performance boost (20-30x!), but composing the formulas based on dynamic data conditions turned out to be a non-trivial task, with complexity growing very rapidly. So we had to go with a dynamic graph, where the inputs of formulas are created dynamically by arbitrary code execution.

Bryntum Gantt 1.0.0 and 2.0.0 are both using this initial version. Using this approach, the Bryntum Gantt scheduling engine currently performs twice as fast as the old engine in Ext Gantt. But we were still not satisfied, as the initial experiments with the static graph showed way more impressive numbers. Additionally, we also identified a number of tough-to-solve problems related to the coding experience of the initial version.

So we re-iterated the same architecture with extra focus on performance and cognitive ease of the programming model. We also reduced the memory usage and added several benchmarks to measure the performance objectively. The latest Chronograph implementation we are using in the upcoming Bryntum Gantt 3.0, provides up to 4 times faster scheduling calculations compared to the old Ext Gantt version.

With all these improvements, we are releasing this version as Chronograph 1.0.0. It will be powering the business logic of the upcoming Bryntum Gantt 3.0.0 and Scheduler Pro 1.0.0. We will continue improving the library and introducing additional features for it, with focus on performance and both user and programmer experience.

An interactive visualization of the graph representing a small Gantt project is shown below:

Lessons learned

As we found, one of the main obstacles of writing code in a dynamic reactive approach was dealing with cycles. Cycles are, unfortunately, a natural way of describing invariants about your data, so they are quite common in the real world.

Another challenge was dealing with throw-away computations. Such computations are assumed to be calculated in the current data state, using regular business logic. Their results however, should be thrown away and not affect the data in such case. For example – adding a link between two Gantt tasks may introduce a cycle, so we want to know how the graph will compute if we add it. Or, a user can open an editor and perform arbitrary editing in it, but then press “cancel” button. In such case, all modifications should be thrown away and the original state of the data should be restored.

Solutions

The cycles problem was solved with a special utility unsurprisingly called cycle resolver. It deals with cycles in a uniform way using static formula declarations. Using information based on user input, it produces cycle resolution – a non-conflicting set of formulas to be used for calculations. This allowed us to cleanup our code drastically, and we see it as a major usability improvement.

We solved the “throw-away” computations problem by introducing data revisioning. One can group several transactions into a revision and switch back and forth between them (undo/redo). Also, one can derive a new branch of the data (similar to how git works), compute in the scope of that branch and then decide if this branch should be “merged” into the current data state or not.

Conclusion

Overall, we found that reactive computations fits very well into our data domain – project management solutions. The benchmark numbers are compared to the EcmaScript 3 codebase, written in plain model/store/observable pattern, which received heavy manual optimizations over the past 8 years. We are beating those numbers several times over. The new type-safe development techniques we use, like mixin classes allow us to reason about the codebase at scale. And the development time required to re-iterate through pretty much the whole previous codebase, was very manageable.

We also see a lot of room for improvement in the UI responsiveness area and have plenty of other ideas of how our codebase can be improved, based on all the research made during the development. Our big goal is to move toward a transactional setup for the multiple concurrent user editing scenario. We are open sourcing Chronograph today in hope it can prove useful for other projects too. We also hope to receive feedback from the community and maybe even contributions & PRs.

All of the features presented in the 1.0.0 release are based on our experience of developing the Bryntum Gantt scheduling engine. If your data domain somewhat resembles project management, chances are that Chronograph will suite your requirements well. Please let us know in such case.

Happy coding!

Resources