[cairo] New API proposal: cairo_restore_status()
Andrea Canciani
ranma42 at gmail.com
Tue Jan 11 03:33:47 PST 2011
A discussion on IRC pointed out the usefulness of some way to reset
the error status of a cairo_t.
Allowing to reset it directly is not likely to be a good idea, because its
state might be invalid or inconsistent. To avoid this issue, it is possible
to only allow resetting the status along with a valid state.
The attached patches provide an implementation of cairo_restore_status():
+ * cairo_restore_status:
+ * @cr: a #cairo_t
+ *
+ * Restores @cr to the state saved by a preceding call to cairo_save()
+ * and removes that state from the stack of saved states. The status
+ * of @cr will be reset to the same value it was in before calling
+ * cairo_save().
+ **/
+void
+cairo_restore_status (cairo_t *cr)
The implementation idea and an use case come from Benjamin Otte, but
it has already been suggested in the ML multiple times.
The rationale for not providing a way to reset the error status
"[...] is that we don't have any
guarantee that we can restore the context to any particular state
after an error occurs. Without a huge audit and a lot of pain to make
every cairo operation atomic, the only conceivable state to restore to
would be the default state. And if we had an operation that did
recover-error-and-return-context-to-default-state then I would argue
that that wouldn't achieve anything that cairo_destroy;cairo_create
does not also achieve, but that it could lead to confusion as people
might not expect all of the state to be reset when recovering from
errors."
(from http://lists.freedesktop.org/archives/cairo/2006-September/007892.html )
It appears that implementing such a feature is not as hard and
painful as it was foreseen.
The main idea is that *conceptually* the status is moved from the cairo_t to
the state and cairo_state() always pushes a state, even if it cannot allocate
memory. In this model, cairo_restore_status() just removes the top state
(thus it restores the status of the previous state), while cairo_restore()
removes the top state, but also copies the status from it to the previous
state.
This cannot cause any observable difference in existing applications,
because cairo_save() and cairo_restore() keep exactly their existing
semantic. It should not even cause bad interactions with libraries
which decide to use cairo_restore_state(), as long as the bracketing
save/restore is preserved correctly.
It might cause some changes in the behavior of applications which
call cairo_save() and then use a library function which was at first
supposed to call cairo_restore() and then was changed to use
cairo_restore_status().
Examples:
OK:
main()
{
cairo_save();
foo();
cairo_restore(); /* this can be changed to cairo_restore_status() safely */
}
BROKEN:
lib_func_which_calls_cairo_restore()
{
change_the_current_state();
cairo_restore(); /* this CANNOT be changed to cairo_restore_status() safely */
}
main()
{
cairo_save();
lib_func_which_calls_cairo_restore();
}
WORKAROUND:
lib_func_which_calls_cairo_restore()
{
/* Adding a pair of internal brackets, solves the observability problem */
cairo_save();
change_the_current_state();
cairo_restore_status();
cairo_restore(); /* this CANNOT be changed to cairo_restore_status() safely */
}
main() {
cairo_save();
lib_func_which_calls_cairo_restore();
}
Details about why the implementation is correct (without the huge audit)
follow:
- a count of virtual failure states is kept in the cairo_t. cairo_save() on an
cr with an invalid status just increments the number of virtual
stack elements.
cairo_restore() on a cr with virtual stack elements just decrements this
number. This allows counting the stack elements without performing
allocation, so it is always successful.
- cairo_save/restore*() never modify a cr with an "excessive" number of
states in the stack. This allows a very clean workaround to operations
on nil cairo_t's and solves the problems that might arise if the counter
for virtual states wrapped around.
- real operations (not just counter incs/decs) are only performed on cr's
whose failure states count is 0
ASSUMPTION: _cairo_gstate_save() can only fail because it cannot
allocate memory for the new state and in this case it will not modify
the current stack in any way.
- cairo_save() will either manage to create a new state and push it on
the top of the stack (resulting in a stack of valid states) or it will not
have enough memory to perform the allocation. In the latter case it
will push a virtual invalid state (i.e. set the failure count to 1 and the
status to an error state)
ASSUMPTION: _cairo_gstate_restore() can only fail if the stack is just
the current state.
- cairo_restore() will simply restore the previous state, polluting it with
the current status (if it is an error)
NB: the new implementation reflects this: cairo_restore() now removes
the top state even if the cr is in an error status
- cairo_restore_status() will restore the previous state, including the
status. If the failure states counter is 0, the status of the previous
state was obviously CAIRO_STATUS_SUCCESS.
- cairo_restore*() always set the cr to an invalid status if the pop
operation on the stack is not legitimate
Would this be an acceptable addition for 1.12?
Should (some of) this documentation be added as comments to the
actual code? Would adding the assumptions (and/or asserting them)
be sufficient?
Andrea
More information about the cairo
mailing list