Transaction Model

Canvas is a scripting language to manipulate documents (templates). It is not only intended to use as a internal model to be able to render things in the browser; you can use it to load documents from the server, modify them and send them back with out showing in the browser nothing more than a progress bar. You can do batch updating of things in the DB like this without the need of asking the server Core team to do it for you. All the power of the engine is there for you to use it in your local machine.

Things can fail

Because of the nature of the interaction between CxEngine in the server and Canvas in the client, some operations are async and there are more places where failures can appear: the internet may be down, the server may be overloaded, etc. Even when you do simple scripting it is useful to be able to work using transactions. Being able to mark valid points in your work allows for a more fine grained treatment of failures. You do not need to redo everything, it is enough to go back til the last valid committed state and do the last part that has failed. In this context, transactions are an important piece of CIL design and are not related to the need of a History feature for the user. You can use CIL without doing any transaction, nobody will force you to do it, transactions are a natural part of the design. A good analogy is to think that you are working with something similar to a revision control system (an old one where you need to explicitly lock resources. So, if you need to modify a figure you will do it like:

figure.lock();
... load resources, modify the figure ...
figure.commit$();

The first lock let others know that this figure is being working on by throwing a document event. Our system allows you to work with figures in parallel, but you can not modify the same figure in parallel.
If you modify the whole document, you will write:

doc.lock();
... modify the doc ...
doc.commit$();

Modifying CIL objects is like changing files in your local repository in the revision system. Your local files (model) are changed until you reach an atomic change set. When you reach that point, you call the .commit$() function to complete the transaction.

If something goes wrong in the middle, you can easily revert your local changes back to the last committed state like:

doc.lock();
... modify the doc ...
... something went wrong ...
doc.revert$();

An important thing to understand here is that the lock/commit$ mechanism is just a transaction scheme and does not generate by itself any history, there is no redo/undo in the picture yet. The mechanism just saves the last committed state to be able to go back to it when you do a revert, so you can program against a failure prone system (like it is the internet, server infrastructure, etc) with better tools. So til here, there is no history, no rendering, no tools disabling, no user feedback. The above description is about our template scripting language.

Canvas

Lets put the Canvas component in the table. This is the main piece used to build browser applications that needs to render templates or interact with user. Now, on top of being able to modify a document, we need to do more things to give a good user experience. The minimal set of things to take care of are:

  • (a) Render the document at moment where it makes sense (avoid showing the guts of batch operations to the user… no jumps around, no flickering because you first loaded something of a given color and later you changed it, etc).
  • (b) Disable all other tools when an operation is being performed so there are no race conditions. Again CIL only allows you to modify one figure at a time.
  • (c) Give visual feedback to show that an operation is being performed, and that the user shouldn’t keep hitting the screen to make it “do something”.
  • (d) Maintain a history of all the operations performed so the user can undo/redo.
  • (e) Select newly added figures.

A possible design is to let the developer deal with these issues. He will need to write code like:

0. canvas.disable(); // b
1. canvas.startFeedback(); // c
2. doc.lock();
3. ... modify the doc ...
4. canvas.selectedFigures(newFigures); // e
5. doc.commit$();
6. history.push(doc); // d
7. canvas.stopFeedback(); // c
8. canvas.enable(); // b
9. canvas.render$(); // a

This is unacceptable, imagine if for just moving a figure we will need almost 10 lines of code.

Canvas tackles this problem by defining that is going to show every committed state of the document. This makes sense because the state at the end of every transaction effectively represent the successful completion of an atomic change set. Canvas listens to the document ‘commit’ event and calls render after it avoiding the need of line 9.

The tools will also be disabled when an operation starts (that is, when someone locks the document to start working on a new change set). Once the change is complete after the ‘commit’, we can safely enable the tools back. Line 0 and 8 can be eliminated.

The same is done for user feedback. As soon as somebody starts an operation we show the user feedback (on ‘lock’) and when the operation is complete we just stop the work in progress feedback (on ‘commit’). Line 1 and 7 are automated.

History is defined as every committed state of the document. Because rendering happens at that point, this means that the user will get a history point for every operation he see completed on the canvas. Canvas automatically creates a history point when the ‘commit’ event is issued removing the need for line 6.

Newly added figures can also be automatically selected when the transaction is done. This creates a standard behavior for user created actions that helps making the UI more coherent. We can then remove line 4.

So, the code to make a proper action ends up being:

doc.lock(); // => Trigger 1, 2
... modify the doc ...
doc.commit$(); // => Trigger 4, 6, 7, 8, 9

The user can just code its action in the same way that he will do to create batch scripts. There is nothing new to learn. Canvas will automatically work.

It makes even more sense when possible failures are analyzed. Every time you call revert$(), Canvas will also be re-enabled and the work in progress feedback will be stopped. No rendering and history needs to be generated here.

An important implication is that clients have a clear pattern to follow to create pre or post-operation events. The client may have other tools to disable/enable that are not part of Canvas. They can listen to the ‘lock’ and ‘commit’ events in the same way that Canvas tools do. Other kind of feedback may be wanted (a progress bar, a loading symbol, a timer). Also, property watchers like a display for the document bounds, or a label showing the current price for the design can no be easily automated to be updated after each commit.

Further details on the lock/commit$ pattern

Conceptually .lock() and .commit() are not really a pair, although they will usually use together. Locking has to do with a limitation of our system, we can no modify the same figure at the same time by two different actions. When you start an action, you have to lock your target (doc or figures). But if your action starts with a modification, you can omit the .lock() call because each modifier will auto-lock like:

figure.pin( ... ); // => auto-lock
figure.commit$(); // => auto-unlock

If you need to load resources before modifying the document in your action you need to be explicit and lock the document:

doc.lock();
... load resources, modify doc ...
doc.commit$();

Our system works like Git in the sense that a commit only modifies the local copy and not the saved document in the server. If you think about the pattern of loading a document, performing some modification and the save it back, then our system will map to Git as follow:

Cx.Document.load$(id) // Git 'checkout'
.then(function(doc){
... modify doc ...
doc.commit$() // Git 'commit'
.then(function(){
... modify doc ...
doc.commit$() // Git 'commit'
.then(function(){
... modify doc ...
doc.commit$() // Git 'commit'
.then(function(){
doc.save(); // Git 'push'
}); }); }); });

Positioning

In CDL documents figures are placed by centering the Anchor reference point to the defined Pin position.

Figure Pin

Whenever a figure is added to a document, its Pin is defined as the (0,0) coordinate. You can also set it at the figure definition:

var figure = Cx.Text({ line: "Phrase"
, ...
, pin: Px(100,50) });

Or change it later using the pin property as usual:

figure.pin( Px(100,20) ); // Or directly figure.pin(100,20);

Also we may change the position of a figure with respect to its current position:

figure.translate( offset ); // Or figure.translate( x,y );

This would change the figure’s position in x horizontally and y vertically.

Other operations can also modify the figure’s position, for example rotation around a given pivot point:

figure.rotateAround(pivot,degrees);

Figure Anchor

To be able to place a given figure in the Pin position we need to define a reference point relative to the figure curves. This point is called Anchor in our system and it is an important piece in building well behaved templates that the user can modify.

The following diagram illustrates the relationship between the Anchor and the Pin. We start with a given shape that is defined by a single Region with a closed boundary. The original points in this curve are the ones in the upper-right corner of the document. The green point is the Anchor of the Shape, in this particular case is an Absolute Anchor. This is the point that will be used to center the figure in the Pin position, sketched in the diagram with a red dot.

Basic Anchor

shape.anchor( Cx.AbsoluteAnchor(pos) );

We use a Relative Anchor to define a good reference point for Texts for example. The reference point will be obtained relative to the bounds of the processed figure (before the final transformation is applied).

text.anchor( Cx.RelativeAnchor(rx,ry) );

The following diagram showcased what happens when the user modify the line of a given Text. The Anchor is defined as Cx.RelativeAnchor(0.5,0.5) in this case. So the center of the bounds is placed at the pin position marked here again as a red dot. The figure accommodates nicely around the Pin.

Text changed

The Anchor of the figure is an object that defines an absolute and a pos properties.

var anchor = figure.anchor();
if( anchor.absolute() ) {
var asolutePos = anchor.pos();
// ...
}
else {
var relativePos = anchor.pos();
// ...
}

Direct positioning

Sometimes, it is useful to avoid the Anchor-Pin model and directly work with the figure bounds. We can use center or align operations to define the pin with respect to other points or objects in the document.

figure.center$(pos); // Or figure.center$(x,y);

The center$ function takes any objects that defines a center (Canvas, bounds, frame or even a list of figures). For example, we can place the figure in the middle of the view with:

figure.center$(canvas); // Center the figure in the middle of Canvas

Read the figures transformations reference to learn about more available alignment operations.

Owned Objects

Cx objects are generally owned by other objects in the document model. For example, the figures in a layer are owned by it and the brush in a contour belongs to that process. These objects implements a owned object protocol to avoid aliasing problems while modifying the document.

When a new object is created, it doesn’t belongs to any other object. So the following snippet will create a free brush:

var brush = Cx.Brush( { rgb: 'FF0000', alpha: 0.5 } );
Cx.assert( brush.owner() == null );

When a free object is used to set another object property, the free object is adopted by the parent object without making any copy:

contour.brush( brush );
// brush is no longer free, contour owns it
Cx.assert( brush.owner() == contour );

If an owned object is used to set a property, the object will be cloned to avoid duplicated references:

contour.brush( figure.brush() );
// contour's brush and figure's brush are different objects, owned by different parents.
Cx.assert( contour.brush() != figure.brush() );

The protocol was selected for being a good compromise between simplicity and efficiency (a possible option could be to use copy-on-write for efficiency or copy-on-set for simplicity). Free or owned objects shouldn’t be something you are thinking on while using the API. It just works, and you can relax about when to clone and object… you never need to do it, the model knows better than us how to avoid getting into troubles. The only caveat is that the following snippet could be confusing:

var brush = figure.brush();
// ...
contour.brush( brush );
brush.color( color ); // Wrong!
// contour's brush and brush are different objects, owned by different parents.
Cx.assert( contour.brush() != brush );

The way to avoid these patterns is:

contour.brush( figure.brush() );
var brush = contour.brush();
brush.color( color ); // Good...

But if you can not use the above pattern, you can add the clone explicitly:

var brush = figure.brush().clone();
Cx.assert( brush.owner() == null );
// ...
contour.brush( brush );
brush.color( color ); // Good...

Invalidation

Figure measurements and the processing needed to obtain its output representation are asynchronous and possibly expensive operations. When you change a property of the figure, for performance reasons, Canvas will try hard to avoid performing the whole computation again by updating its internal measurements and output representation locally whenever possible. Expensive updating operations will not be triggered every time the model changes. The figure remembers its invalidation state waiting for the next update$. Model changes work in batch:

figure.pin( canvas.center() );
figure.matrix22( Mx.scale(2,2) );
figure.brush( Cx.Brush({ rgb: 'FF0000' }) );
// Now the figure is in an invalid state, measurements and the output representation
// that is rendered in Canvas are outdated
figure.update$() .then(function(){
// Now the figure measurements and its output representation are up to date,
// but the Canvas has not been touched
});

The model will automatically invalidate itself when using the public API so all these optimizations will work behind the scene and apps shouldn’t need to worry about them. But direct manipulation of primitive types needs a manual call to invalidate:

figure.pin().x = 20;
figure.matrix22().s00 = 2.0;
// figure is in an undefined invalidation state now
figure.invalidate();
// good, everything will work now

Avoid at all cost this pattern, apps should not need any .invalidate call at all if they treat primitive types as whole values always. You have to think that a point is actually equivalent to a number or a string in JavaScript. We can not expect that the model will work if you change a character in an array like this:

figure.name()[3] = 'a'; // this should strike you as the same as figure.pin().x = 20

So every time you change a point, a matrix or other primitives types in the model, just use them as immutable objects passing around:

var center = canvas.center();
figure.pin( center );

The list of primitives types in Canvas API is: number, integer, angle, magnitude, string, point, matrix, frame and bounds. They are easy to recognize because their members are plain JavaScript variables (.x and .y, .p0, .s00, etc).

Owned mechanics

Cx objects are generally owned by other objects in the document model. For example, the figures in a layer are owned by it and the brush in a contour belongs to that process. These objects implements a owned object protocol to avoid aliasing problems while modifying the document.

When a new object is created, it doesn’t belongs to any other object. So the following snippet will create a free brush:

var brush = Cx.Brush( { rgb: 'FF0000', alpha: 0.5 } );
Cx.assert( brush.owner() == null );

When a free object is used to set another object property, the free object is adopted by the parent object without making any copy:

contour.brush( brush );
// brush is no longer free, contour owns it
Cx.assert( brush.owner() == contour );

If an owned object is used to set a property, the object will be cloned to avoid duplicated references:

contour.brush( figure.brush() );
// contour's brush and figure's brush are different objects, owned by different parents.
Cx.assert( contour.brush() != figure.brush() );

The protocol was selected for being a good compromise between simplicity and efficiency (a possible option could be to use copy-on-write for efficiency or copy-on-set for simplicity). Free or owned objects shouldn’t be something you are thinking on while using the API. It just works, and you can relax about when to clone and object… you never need to do it, the model knows better than us how to avoid getting into troubles. The only caveat is that the following snippet could be confusing:

var brush = figure.brush();
// ...
contour.brush( brush );
brush.color( color ); // Wrong!
// contour's brush and brush are different objects, owned by different parents.
Cx.assert( contour.brush() != brush );

The way to avoid these patterns is:

contour.brush( figure.brush() );
var brush = contour.brush();
brush.color( color ); // Good...

But if you can not use the above pattern, you can add the clone explicitly:

var brush = figure.brush().clone();
Cx.assert( brush.owner() == null );
// ...
contour.brush( brush );
brush.color( color ); // Good...

Canvas Actions

Canvas Actions

Canvas offers manipulation functions to modify the document and ask the user to use .commit$() when the transaction is finished:

var doc = canvas.document();
var parts = canvas.selectedFigures();
var group = Cx.Group.create(parts);
doc.replace(parts,group);
doc.commit$();

Canvas will automatically lock itself, give feedback to the user and render the results after the transaction is committed.

To further help in the building process of new clients, Canvas API provides cooked in actions that covers the most common needs of a template application.

Instead of the code above, you can just wrote canvas.action('group') to get a function that will group the selected figures when called. You could use it like:

$('#groupButton').click( canvas.action('group') );

Actions are function objects and expose useful query functions:

var action = canvas.action('group');
var id = action.id(); // => 'group'
var info = action.info(); // later...
var tooltip = action.tooltip(); // later...
if( ! action.enabled() ) {
// Disable your UI button
}

When you create an action, you can modify its target. Instead of applying the action to the selected figures, you can choose to target the context figures (defined as the selected figures when the selection in not empty and all the figures in the layer otherwise).

var action = canvas.action('group', { target: 'contextFigures' } );

You can also give a callback that will be issue once the action is complete and everything has been rendered in the Canvas:

var action = canvas.action('group', {
callback: function(group) {
// Do something specific to your client with
// the newly created group...
},
scope: this
});

The current list of build in actions is (these are the functions exposed in the selection toolbar in the HUD, the list will quickly grow to cover transforms and other usual actions):

  • createMultiPartText
  • breakMultiPartText
  • group
  • ungroup
  • editEnvelope
  • editPowerClip
  • zoomIn
  • duplicate
  • remove

Array Like Interface

Canvas arrays needs to fire events when they are modified so others can sync with them and expose extra functions to simplify
their use: add, remove, replace. Canvas arrays are Array-like objects, all the standard array functions are exposed and you can use them as you will use normal array:

// Plain iteration
var figures = group.figures();
for( var n = 0; n < figures.length; n++ ) {
var f = figures[n];
// ...
}
// Lo-dash iteration
_.forEach(group.figures(),function(f){
// ...
});

Only the a[n] = o write interface needs to be avoided. Instead you need to use replaceAt or at.

// Wrong!
figures[2] = text;
// Good...
figures.replaceAt( 2, text );

The problem with using figures[2] = o, will just modify the 2 property of figures and nobody will know that this change has happened. For example, when you use replaceAt, there is code that deletes the old figure view and adds a new one (all this sync code is handled behind the scene). There is also another problem, if you set figures[6] in a 4 element array, the length should change but it will still be 6.

There are running standards to try to identify array like objects so it is easier to code utility libraries that are supposed to work with them. Underscore.js takes the approach of defining that if object.length is a positive number, then the object is array like. You will see checks in their code base like: obj.length === +obj.length.

Lo-dash and Undercore.js and other functional libraries do not modify the arrays you pass to them, they create new arrays with the results (_.map, _.filter, etc). Even things like _.shuffle. An array-like object then works fine on them, you can filter the arguments object for example, or a jQuery array.

Rationale

In Javascript we can not create a class that derives from an Array object, with different compromises. We played with that ideas but we couldn’t impose the compromises they propose to Canvas (the closest one).

jQuery actually uses something similar to this, at least in the final structure of their arrays.

var buttons = $('.button');

buttons is an Array Like object (like the ‘arguments’ function var). It has a length integer property, and their elements can be accessed using buttons[n] as a normal array. Because of this, every library that is coded to be able to handle javascript array like objects will work perfectly with them. You can use:

_.forEach( buttons, ... );

jQuery is not deriving from Array. Their array like object is actually just a normal object that has a length property and a ‘0’, ‘1’, ‘2’, etc property. Like this:

var jArray = { length: 3, '0': a, '1': b, '2': c };

Or equivalent:

var jArray = {};
jArray.length = 3;
jArray[0] = a;
jArray[1] = b;
jArray[2] = c;

So this works fine:

for( var n = 0; n < jArray.length; n++ ) {
var v = jArray[n];
// ...
}

Performance

Ecen if using an object {} instead of an array [] has performance implications, the best argument to show that it is not that bad is that jQuery arrays are actually created like this. There is a test page in jsPerf. You can look at the bar graphs, the three first are Array Like (assign, create and for each), the second three are Native Array. Some conclusions:

1) Chrome is awesome. Using plain objects is actually faster than native arrays.
2) Firefox also have better iteration and assign behavior in Array like objects.
3) IE is more consistent with ~2.5x performance penalty.

The test are using 1000 elements arrays, quite above the mean size of our own arrays (10 figures are big arrays in our system). So it seems that the price tag is quite low, given that the iteration cost almost disappear in the big picture of the computations Canvas or clients are performing.