This is an Engineering Notebook post. Feel free to ignore it unless you have a deep interest in bug #69.
Bob has just reported that the post scan works for him. That's great, but I would like to eliminate the post scan: it is *almost* never needed and it slows down *every* load of a .leo file.
This post discusses a way around the post scan by adding a new field to gnx's. I'm dithering about whether to do this: It's a big change to the format of gnx's and it changes Leo's crucial read/write code. Don't panic: nothing has been decided.
===== Background & terminology
Recall that we say that an invocation of Leo (an instance of
g.app) is **based** on the timestamp computed when *that invocation* of Leo starts.
Bob's previous fix guarantees, as much as possible, that no two
invocations of Leo are based on the same time stamp. The
post-scan requires this!
The post-pass adjusts the lastIndex ivar of the singleton g.app.nodeIndices (ni) object so that all newly-created gnx's are guaranteed to be unique, regardless of other loaded .leo files in the present invocation of Leo.
===== The idea: one more field in the gnx
Leo's gnx's would change from::
id.timestamp.n
to::
id.timestamp.fn.n
where fn is the **file number** for the commander (.leo file) that is creating the gnx.
It is easy for Leo to assign a new file number, say c.file_number, when creating each commander.
===== Why fn solves bug #69 without a post pass
The fn field is unique for each commander, so it guarantees that gnx's created in separate commanders can never collide, even though n starts at 0 for each commander. It's really as simple as that.
====== Details
We must ensure that we can read and write gnx's properly.
The crucial requirement is that the commander c must be available for all calls to ni.getNewIndex so that c.file_number is available. Happily, this is so. In fact, the only "real" calls to ni.getNewIndex are in v.__init__, fc.getLeoOutlineFromClipboard and at.writeNodeToString. c is available in each case.
v.context is a commander, but context might be None. In that case, ni.getNewIndex should use fn = 0, which in turn means that "real" commanders should have fn >= 1.
Amusingly, there is also a call to ni.getNewIndex in ni.toString, but it turns out that that call is never used! Indeed, there used to be lots of calls to ni.toString. Most of these calls are all gone, although I left them commented out throughout Leo for reference.
Only one call to ni.toString remains, in createSaxVnode::
v.fileIndex = ni.toString(ni.scanGnx(sax_node.tnx,0))
Evidently, ni.toString is more complex than it needs to be. Formerly, ni.toString handled all kinds of input, but now it only ever called once, with ni.scanGnx delivering a 3-tuple: theId,t,n. So we could eliminate ni.toString entirely, and replace it with ni.tupleToString::
def tupleToString (self,theId,t,n):
'''Convert a gnx tuple to its string representation.'''
return g.toUnicode("%s.%s.%s" % (theId,t,n))
Even with the new gnx's, ni.scanGnx could *still* deliver a 3-tuple theId,t,n, with n having the form "fn.n". That's why tupleToString must use %s instead of %d to convert n.
===== Risks
1. ni.scanGnx must handle gnx's of the form::
id.timestamp.fn.n
and deliver (the string) fn.n as the value of n. This is in *addition* to handling all forms of legacy gnx's. This is *not* a slam dunk. A very careful set of unit tests will be required to ensure that the new ni.scanGnx handles everything in a bullet-proof manner.
2. I must make *sure* that there are no problems before releasing any
code. The last thing I want are new-style gnx's in .leo files that
aren't handled properly. Getting rid of them (or adding permanent work-around code) would not be pleasant.
BTW, Leo's write logic remain completely unchanged: v.fileIndex is always as string. The write code just writes that string, whatever its format.
===== Conclusions
It's tempting to eliminate the post-pass, but risks must be handled properly.
I am leaning to adding the extra field--I can experiment without changing Leo's core much. Furthermore, this is an opportunity to retire ni.toString entirely, replacing it with something simpler.
There is plenty of time for further reflection. Your comments, please.
Edward