Adventures in plugins: Disk Clock 2.0

The first stage of the massive Disk Clock refactoring project is complete. Features and disks have been slowly coalesced and excised into separate files. The change is so thorough I’m starting to call it 2.0, even though it isn’t a publicized release. This implies that the public release will be some 2.n, but so it goes.

From around 2200 lines, the main Javascript file is down to 700, though framing ceremony and modularity handling have undoubtedly increased the total size. In fact zip file size has gone from 147kB to 184kB, which says something about modern software engineering practice and the disappearance of small applications. The next stage is to get a switchable runtime/compiletime require mechanism working so I don’t need the great litany of script tags in the html document.

A Clash of Worlds

One of the background tasks as I’ve been working on file modularity is to eventually make DC instanceable without resorting to the previous trick of re-running the entire closure. This had led to two core namespaces. ‘diskclock’ holds the common portions, while ‘app’ holds the more transient instance state. In some cases (such as disks) app holds prototypal derivatives of objects in diskclock.

This becomes a significant challenge for a program that grew up as a living thing. No longer can a piece of code reach across the program to grab any state or function it needs. Handling references to the app object becomes a series of puzzles, where one tries to either remove it, or thread the object (or a necessary sub-part) through to the point of use. In the case of some callbacks and event handlers, binding a closure is the only way.

Even with all the places I’ve purged or passed, the app object still bothers me a bit. It feels like I haven’t so much eliminated global state as stuck a prefix on it, and many places still reference it as an application global, little pieces of technical debt to paid off later.

Pluggable Settings

There is no point in having settings for disks that don’t exist, and the option to switch to disks that are not available would only confuse. Available options have to be optional, so I started gathering up disparate pieces into file-locality. This eventually became structural locality as the plugin system took place.

Getting Committed

One thing that had to be gathered up was the effects of each settings. It had all been centralized in one function. This had to be broken up and attached to each setting, as a commit method, so it was only implemented when needed.

One of the things this required was creating plugin specific state. Since plugins are installed in the diskclock namespace before any instance exists, they have to declare their mutable state, so it can be created in each app instance.

Reconfiguration Served Daily

One feature that had to be shuffled into plugins was the daily recalculation of solar noon and other daylight values. This involved a shared function and a common pattern. I found that I could completely extract the recalculate-daily pattern. For the time being, it’s included with the location plugin, since the feature is only used with daylight-aware disks.

Playing in Major Keys

One thing that got a little entangled code scheduled for modularization was keyboard handling. Specifically, the shift key to switch the disk set toggle state. I had to extract the modifier flags to a keys object.

Multidrop Events

Putting indirection into the event handling also required the extension of the new event handling system to handle multiple receivers. I set up the API with this in mind, but deferred the implementation until it was needed.

The Mask and the Mirror

One of the tricky bits of a dynamic object system is how to implement the dynanisism. Ruby for instance, has methods, send, and id among others. Two of those are important enough to be aliased as __send__ and __id__, but if you have a name collision with methods, you are on your own. Python solves this by making all system methods double-underscore (or ‘dunder’, as I heard in some conference recordings) Another pattern to resolve this problem to us a mirror, a second object that allows you to operate on the first.

I wanted to use direct symbols for my events, so I tried using a mirror pattern. It’s probably overkill for this case, but the whole project is kind of a programming exercise. It does have the nice effect of different names for different purposes, i.e. app.events.receive(‘foo’, …) and app.fire.foo()

Fighting the Impossible Bug

You know the legends. “// Don’t remove this line” Bugs where looking at the problem makes it disappear, but it returns as soon as you look away. I think I may have a hit kink in Firefox’s trace trees. Some combination of long namespace paths brought on by modularization, and the double indirection of the browser’s event system and my event system made it remove a conditional that it shouldn’t have, and I lost the ability to toggle between single and double disk sets. I went ahead and committed a version with this problem, and tagged it togglebug as a curiosity.

The resolution of sorts was actually useful in and of itself – the permanent toggle mode flag became a saved preference instead of a piece of transient state, thus putting it in a different object than another piece of closely interacting state.

Prelude to Heresy

Pulling code out of it’s native environment can be tricky. It loses all it’s built up context. I had to extract a piece of prelude just so I could safely define disks without having to build up the whole namespace chain every time. Eventually I turned to a piece of Javascript heresy to avoid filling the screen with namespace dereferences: the much maligned with statement, which allowed me to set up the common symbols used for disk definition once and then use all 30 times over.

Are Disks Plugins?

For a while I expected there to be a disks directory and a plugins directory. In the end I made disks one possible feature of plugins, so that I would only need one loading mechanism. On the downside, it gave me a single file name space, and I currently have the Birthday disk included with the birthday plugin to avoid the name conflict.

Half and Half

Finally, though I put it off during the refactoring, I had to make a feature change. I had noticed that the day disk with it’s day and night was easier to read then most disks, with their four colored sections. So I switched to a half colored presentation. After looking at it a bit, I decided that having color on the root side worked better, although it was sort of opposite the day/night pattern. However, I found that reading the 4-hour disk became harder, because it had been easy to pick out the hour sections. So I restored gradients for small-count disks, but shifted the colors over so the dark section is roughly the same as the other disks.

Posted Tuesday, November 3rd, 2009 under Devlog.

Comments are closed.