|
|
||
Chet Haase's BlogJanuary 2008 ArchivesCrystal MethodologyPosted by chet on January 17, 2008 at 01:19 PM | Permalink | Comments (9)Not only am I a huge fan of software design patterns, I'm also strongly supportive of process in software. Process makes us strong. Process enables us to achieve highly metric-driven quality levels. Process allows us to attend meetings throughout every day, ensuring that any coding time we get will be that much more intense because it is necessarily so short and focused. And finally, process allows us to draw pretty charts and graphs on endless presentations. Or, as I like to say it every morning when I wake up, "Software Is Process." For without the process, where would software engineers be but in their offices, cranking away code, pretending to be productive? Now that people have had time to understand and incorporate the important patterns I discussed in my earlier Male Pattern Boldness article, it seems high time to tackle the larger topic of Software Processeseses. The field of Software Methodology is rife with theories, names, buzzwords, and descriptions that improve our tawdry geek lives constantly by letting us focus on that which makes us most productive: studying and then trying to implement completely new software processes to attempt the same job that we could have been actually doing in the meantime. HysteresisFirst, here is a historical perspective. Traditional software implementation was a rather simple and straightforward process, resembling something like the following:
But this process was flawed by its inherent simplicity; if everyone could understand and follow it, what hope did the industry have in creating more meetings and process documentation? Changes were suggested for this methodology, resulting in more comprehensive models like this one:
Eventually, some rogue elements of the community came up with a different process model, based on fundamental programming philosophies:
But the field has been somewhat quiet lately, leading to more coding than is really good for us, so I feel motivated to introduce some of my favorite new process models into the community. There are obvivously more than I can cover in a simple article like this, probably deserving an entire bookcase of unread tomes, but these will have to do for now as I have to go brainstorm with my team in an offsite about how we can be more effective (this week's task is to come up with a mission statement). ScumIn the Scrum development model, the focus is on short iterations and constant communication. The Scum model, however, focuses on the individual. In particular, each engineer works completely on his or her own, producing code at an alarming rate. Changes are integrated and merged willy-nilly, causing untold breakage due to the complete lack of communication. At each fault, the offending code, putback, and engineer are indentified as scum and are tossed out of the project (this step is called "Hack-n-rack"). The resulting code and team are thereby better over time, having weaned out the weak members through natural selection. As it's inventor, Dr. Feen Bookle, PhD, Mrs, QED, JRE, said at its unveiling at the Conference On Terribly Important Academic Philosophies and Theories on Software Process Methodology Discoveries (CTEAPTSPMD), "Scum will always float to the top. Skim it off and you've got just the juicy bits left. Plus the bottom-feeders." Fragile ProgrammingAs I mentioned in my earlier article, Fragile Programming is an important element of the Delicate software pattern. It is related to Agile programming, which is typified by small development cycles that are designed to meet the reality of constant requirements churn. But in the newer, and therefore better, Fragile methodology, each iteration is started from scratch based on only the latest requirements. Instead of building upon the existing code base, which has presumably attained some amount of stability and robustness through its existence and evolution, Fragile projects rewrite the entire project anew in each cycle, thus generating brand spanking new products that adhere more closely to the latest whim of the client. This process results in software that is tuned exactly to what the client asked for, and the resulting instability of the code base can thus be blamed directly on the client, allowing a convenient excuse for the failing development team. Conference Drive Development (CDD)This brave new methodology, suggested to me by Charles Nutter, looks like it has serious potential. As anyone who has ever developed software and demos for a conference before knows too well, there's nothing like a looming keynote or session deadline to enforce good coding standards, carefully thought-out APIs, and integrated feedback from the larger community. Developers typically end up at conferences with completely spec'd out products that only lack for a smattering of documentation before being released out onto the masses like white cat hair on a black sweater. It's just that whole "Quality" part of the release process that drags it down for the next couple of years and keeps it from being an instant product reality. With the increasing volume of conferences around the world, I see CDD as becoming more and more interesting. Products that hinge releases upon the mad rush of pre-conference development will be able to ship new versions every month, or even faster. Sure, they'll go out with bugs, no documentation, and a complete lack of testing, but the demos will rock! Rabid Application DevelopmentRapid Application Development helped move developers from the more stodgy development processes of earlier decades when people were dumber onto quicker models of development, based on fast prototyping work. Rabid Application Development takes this a step further. Instead of using prototypes as ideas to help with future, more stable work, the prototypes are the product, and are checked in as soon as they are complete, or in many cases, sooner. The key to Rabid Development is to keep the engineering team going at such a frenetic pace in coding and checking in code that nobody, including the client, ever realizes what a complete load of crap they've produced. This model is used throughout most university CS courses and has become the default process basis for all homework assignments. It is also the mainstay of software startups everywhere. CliffDeveloped as a response to the Waterfall model, where software goes through various stages of development during its life cycle, the Cliff model leaps suddenly from the starting point (known as the Hairbrained Idea) to the end (known as the Product, but informally referred to by Cliff teams as the Corpse). These projects are generally executed overnight with several pots of coffee by developers with no social lives. They start from an idea in a chat message during a World of Warcraft session and result in the engineers checking in the Corpse by the start of work the next morning. No output of the Cliff model has ever been useable outside of negative case studies, but interest in this approach persists by those engineers still lacking in better things to do at 2 AM. OopsRelated to Object Oriented Programming (OOP) methodologies, the Oops system seeks to develop reusuable objects, but never quite makes it. Previous components are never exactly what they need to be, thus requiring that the functionality be rewritten from scratch, resulting in a general "Oops!" exclamation from the teams involved. But as all engineers know, and all managers hate, it's always more fun writing things from scratch anyway. The Oops methodology is the one most favored by all programmers. Clean Your Room (CYR)This methodology comes as a response to the Cleanroom Software Engineering process, which strives to produce software whose quality can be certified. Clean Your Room, on the other hand, takes a different tack, basing its philosophy upon the teenage kid tenet: "Why should I clean my room when it's just going to get dirty again?" In this process, the focus is upon implementing cool, new features (called Wall Posters after the typical decorations in most teenager bedrooms) and not on tests, documentation, or bug-fixes. The belief with these other traditional elements of software products is that as the software changes, tests would be obsoleted, documentation would have to be rewritten, and new bugs would be introduced. So what's the point in doing the work twice? All CYR projects are run under the theory that eventually, when the product is completely done, the other non-coding aspects of the product will eventually be seen to (hopefully by someone else). Since no CYR project has ever reached completion, this has yet to be proven in practice. Testosterone-Driven DevelopmentLike its namesake Test-driven Development, which is known for the requirement of engineers writing tests before code, Testosterone-driven Development focuses on testing first. But it does so in an an extremely aggressive manner, requiring every engineer to produce entire test suites, test frameworks, and test scripting languages for every line of product code written, including whitespace and comments. Engineers violating this contract are taken out back where they have the crap kicked out of them. Any code found to have bugs not caught by tests results in the offending engineer having to go three rounds with the project manager (with the engineer being handcuffed and blindfolded during the match). Finally, any bug found in tests will result in the engineer's body never being found again. Expectations are high from this newcomer to the field, although to date none of the products using this process has left any survivors. I realize that the above list is quite a small sampling of the many wonderful methodologies which are possible. But I hope you will try at least some, or maybe even all, of these out in your team, throwing the entire project into disarray every couple of years while you reinvent everything. If you find either success or complete abject failure in your attempts, I encourage you to write a paper, speak at conferences, and publish books on the topic. Then form a consulting company that helps other development teams try to adopt the same methdologies. Software products are a journey. They aren't just about the code you see at the end; they're about the path taken to get there. And the paths not taken. And the signs on the road. And the maps used. And the gas station attendants asked for directions when you got lost. And the hikes through the wilderness when you ran out of gas. And the ceaseless talk radio that your parents played while you got carsick in the back all over your sister. Process is the car that gets you there; you just need to pick the right one, pay too much for it, and then build it from scratch first. Scene Graph: Demo-liciousPosted by chet on January 17, 2008 at 10:28 AM | Permalink | Comments (3)In the dark ages, before the Scene Graph project was public, development on the library was coupled with development of demo applications. These demos were written for various reasons: to test new functionality, to get a feel for the API and development experience, to have benchmarks for performance tuning, and to have stuff to show when we talked about it at conferences. When we made the Scene Graph library public, we also wanted to make our demo applications public. But there was one big problem; unlike the library itself, we hadn't written those applications with the public, or even anyone but ourselves, in mind. And publishing code that you haven't actually sanity-checked can be unwise. So we posted the library with just the Nodes demo (quickly cleaned up for the occasion, like putting a tuxedo on a homeless guy), and the intention of going back to get the other demos in a publishable state. Now, a month later, we think we're there; we've created a new project on java.net to host all of our public demos, including the Nodes demo already published as well as the jPhone demo discussed in my previous blog entry. You can go to that project, run the demos, see the code, sync up to the source base, and play around with all of them. Feedback (on the demos or the Scene Graph in general) should use the forums and aliases on the Scene Graph project, not the demos project (at least until we figure out how to disable those elements on that project). It's helpful to funnel all of the feedback through one channel. So what are you reading this for? Go to the scenegraph-demos project and enjoy the new stuff. It's demo-licious. Write a Phony Application ... Or Dial TryingPosted by chet on January 16, 2008 at 01:29 PM | Permalink | Comments (5)A few weeks ago, in a quest for more performance benchmarks, the scene graph team asked for a demo that was representative of some of the graphics and animations that might be typical in a consumer-oriented application. I had run across an interesting video on the Apple site recently, iPhone: A guided tour. The device in the video had ideas of what we were looking for; fades, moves, scales, transitions... all the whizzy animations that consumers love. So I took a whack at doing something similar with the scene graph. Introducing: JPhone: Okay, so it's not really the same thing. For one thing, I just used some icons I had lying around which don't look as good at the large size required for this interface. I'd love to use the iPhone icons instead, but I'm still waiting for the Apple lawyers to call me back. And waiting. And waiting. (Steve?) Also, I guess I have to admit it: the jPhone demo is not a phone. Even if you pick up your monitor and hold it next to your ear, all you'll hear is the sound of your brain screaming in pain from the pixel radiation. (And the ocean. Isn't it funny how you can always hear the ocean? Or maybe it's just sound waves.) But mimicking a phone wasn't the point; it was all about the user interface. Finally, it'll become obvious when you start to use it that, well, there are no 'applications' behind the icons; it's just the same dummy screen that comes up again and again. But once more, my petty rationalization comes in handy; the demo was supposed to be about GUI animations, not actual functionality. But hey, It's A Demo! Anyway, on with the article. Note that my discussion below is all about the jPhone demo. I don't actually know how things operate under the hood on that phone thingie from Apple; all I know I learned from watching that video. But I do know how the jPhone demo works, so I'll stick to that. The GUI: Main menu, tray menu, and applicationsThere are three different GUI areas in the display, used at different times. What I call the "main menu" is the grid of icons arrayed out across the first screen, starting at the top. Each of these icons accesses a different application (each of which looks uncannily similar in JPhone) when clicked. The "tray menu" at the bottom has four additional icons, which are much the same as the icons in the main menu, but for common functionality that the user might want to access more frequently. Finally, the "application" screens are those displays that come up after the user clicks an icon. For example, when the user clicks on the Calculator button, they probably expect a calculator application screen to become active. Main Menu
The main menu of the application consists of a grid of icons, four columns wide.
The menu is created in the cleverly-named
Each "icon" consists of both an image and a text caption. So for each icon object
in the scene graph, we create an The group is created in a single line as follows: SGGroup imageAndCaption = new SGGroup(); The image node takes a few more lines, as we need to scale the image appropriately and then set the image on the node:
SGImage icon = new SGImage();
try {
BufferedImage originalImage =
ImageIO.read(getImageURL(mainMenuIcons[iconIndex]));
BufferedImage iconImage = new BufferedImage(ICON_SIZE,
ICON_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics2D gImage = iconImage.createGraphics();
gImage.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
gImage.drawImage(originalImage, 0, 0, ICON_SIZE, ICON_SIZE,
null);
gImage.dispose();
icon.setImage(iconImage);
imageAndCaption.add(icon);
} catch (Exception e) {
System.out.println("Error loading image: " + e);
}
(Note that our image scaling assumes that a one-step bilinear scale will give us
sufficient quality, which it does in the case of up-scaling the smaller images we're
using for icons. For a more general scaling solution that gives dependable quality and decent
performance, check out Chris
Campbell's article on
The Perils of Image.getScaledInstance()).The text node takes a few lines to set up the text rendering and location attributes appropriately: SGText textNode = new SGText();
textNode.setText(iconCaptions[iconIndex]);
textNode.setFont(captionFont);
textNode.setFillPaint(Color.LIGHT_GRAY);
textNode.setAntialiasingHint(RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Rectangle2D rect = textNode.getBounds();
textNode.setLocation(new Point2D.Double(
(ICON_SIZE - rect.getWidth())/2, ICON_SIZE + 10));
imageAndCaption.add(textNode);
To position each icon in the menu, and to allow the icon to be moved later when it animates, we create a transform node as the parent of the icon and add that node to the scene graph: SGTransform.Translate transformNode = SGTransform.createTranslation(xOffset,
yOffset, imageAndCaption);
rootNode.add(transformNode);
Tray Menu
The tray menu, initialized in the First, the tray needs to be positioned on the screen, so the tray group needs an overall transform. Also, we will fade the tray in and out during transitions from and to the application screen, so the tray group also needs a filter node to handle fades. These nodes are set up as follows: final SGGroup trayGroup = new SGGroup();
SGComposite opacityNode = new SGComposite();
opacityNode.setOpacity(1f);
SGTransform trayTransform = SGTransform.createTranslation(0,
SCREEN_H - (1.5 * ICON_SIZE), trayGroup);
opacityNode.setChild(trayTransform);
rootNode.add(opacityNode);
Next, we have an interesting background for the tray that consists of a basic gray gradient with a solid darker gray underneath the captions. We set this up with a couple of shape nodes and a transform node to position the darker area appropriately: // Set up the basic tray background
SGShape trayBackground = new SGShape();
trayBackground.setShape(new Rectangle(SCREEN_W,
(int)(ICON_SIZE * 1.5)));
trayBackground.setMode(SGShape.Mode.FILL);
trayBackground.setFillPaint(new GradientPaint(0f, 0f, Color.DARK_GRAY,
0f, (float)(ICON_SIZE * 1.5), Color.LIGHT_GRAY));
trayGroup.add(trayBackground);
// Set up the darker background for the captions
SGShape captionBackground = new SGShape();
captionBackground.setShape(new Rectangle(SCREEN_W, 20));
captionBackground.setMode(SGShape.Mode.FILL);
captionBackground.setFillPaint(new GradientPaint(0f, 0f, Color.DARK_GRAY,
0f, 10f, Color.GRAY));
SGTransform captionBGTransform = SGTransform.createTranslation(0,
(1.5 * ICON_SIZE) - 22, captionBackground);
trayGroup.add(captionBGTransform);
The icons themselves are set up just like those for the main menu, adding themselves
to the Application
The application objects, created in the // App screen is just an image, scaled/faded in when it becomes active
final SGImage photo = new SGImage();
// ... code to load/scale/set image removed for brevity ...
photo.setVisible(false);
SGComposite opacityNode = new SGComposite();
opacityNode.setOpacity(0f);
opacityNode.setChild(photo);
AffineTransform fullScale = new AffineTransform();
AffineTransform smallScale =
AffineTransform.getTranslateInstance(
SCREEN_W/2, SCREEN_H/2);
smallScale.scale(.1, .1);
smallScale.translate(-SCREEN_W/2, -SCREEN_H/2);
SGTransform scaleNode = SGTransform.createAffine(smallScale, opacityNode);
fullScale = new AffineTransform();
rootNode.add(scaleNode);
Note that the application node starts out invisible (because it is hidden until triggered by a mouse click on one of the menu icons), completely transparent (until it is faded in), and scaled to 10% of its true size (until it is scaled in during a later animation). AnimationsThe objects set up above were necessary, but the fun part is really the animations that drive the application. The animations are all triggered based on user clicks. A click on the main menu icons will run animations on the main menu, the tray menu, and the application screen simultaneously. A click on the application screen will run all of the same animations - in reverse. Let's see how we set up and run these animations. There are two Timelines create to run these animations (recall from an earlier blog entry that Timeline is a convenient grouping mechanism for animations): Timeline menuOutTimeline = new Timeline();
Timeline menuInTimeline = new Timeline();
Main Menu AnimationThe interesting part in the main menu animation is in trying to guess what's going on in the Apple video (and the iPhone interface). As an engineer, I would expect the icons to move in a linear fashion, sliding horizontally or vertically, perhaps the same every time, or maybe with the direction set based on which icon was clicked. In fact, one of the engineers on the team once rewrote my animation code to do just that, assuming that there was a bug in my code and I must have meant to have this straight-line animation instead of the effect I had implemented. But if you look closely at the video (or, heck, at that iPhone you have in your pocket), you'll see that the icons move in diagonal trajectories off of the screen, all shooting off in different directions. For example, here's a stop-action view captured from the video:
Also, it looks the same every time, no matter which icon is clicked. I would then assume (and have implemented the code this way in jPhone) that the icons all shoot away from one central point on the screen. But that doesn't appear to be the case. Anyway, I think the animation for jPhone's main menu looks pretty good, if not exactly what they happen to do on that other device.
The basic idea in jPhone is to calculate the movement vector based on some movement
"center" ( double xDir = xOffset - xCenter;
double yDir = yOffset - yCenter;
We can then calculate the new offscreen position of the icon ( Finally, we can create an animation that will move the icon from its position in the main menu to this offscreen position as follows: Clip iconClip = Clip.create(MENU_OUT_TIME,
transformNode, "translateX", xOffscreen);
iconClip.addTarget(KeyFrames.create(
new BeanProperty(transformNode, "translateY"),
yOffscreen));
menuOutTimeline.schedule(iconClip);
The iconClip = Clip.create(MENU_IN_TIME,
transformNode, "translateX", xOffset);
iconClip.addTarget(KeyFrames.create(
new BeanProperty(transformNode, "translateY"),
yOffset));
menuInTimeline.schedule(iconClip);
Tray Menu Animation
Tray menu animation is simpler; we just fade the tray out and back in when applications
become active or inactive. To fade the tray out, we create an animation on the Clip fader = Clip.create((int)MENU_OUT_TIME, opacityNode,
"opacity", 1f, 0f);
fader.addTarget(new TimingTargetAdapter() {
public void end() {
trayGroup.setVisible(false);
}
});
menuOutTimeline.schedule(fader);
Note that we're actually doing two things here; we're fading out the node from opaque
to completely transparent, and we're setting the visibility of the node to When an application becomes inactive, we run the reverse animation on the tray menu to make it visible and fade it in: fader = Clip.create((int)MENU_IN_TIME, opacityNode,
"opacity", 0f, 1f);
fader.addTarget(new TimingTargetAdapter() {
public void begin() {
trayGroup.setVisible(true);
}
});
menuInTimeline.schedule(fader);
Application AnimationFinally, we need to animate the application screen. These animations are similar to what we've seen before, although in this case we are both fading and scaling the application screen: final Timeline appAnims = new Timeline();
Clip fader = Clip.create((int)MENU_OUT_TIME, opacityNode,
"opacity", .1f, 1f);
Clip scaler = Clip.create((int)MENU_OUT_TIME, scaleNode,
"affine", smallScale, fullScale);
appAnims.schedule(fader);
appAnims.schedule(scaler);
fader.addTarget(new TimingTargetAdapter() {
public void begin() {
photo.setVisible(true);
}
});
The application animation is also how we start tying the different animations and
events together. First of all, we kick off the overall fader.addBeginAnimation(menuOutTimeline); Next, we add an attribute to each icon that tells it which animation it is associated with: appIcon.putAttribute("startAnim", appAnims);
Finally, we add a mouse listener to each icon that will listen for clicks and start the animation appropriate for that icon: appIcon.addMouseListener(appStartListener); where the mouse listener is defined to start the animation that we stored as an attribute on the icon in question: SGMouseListener appStartListener = new SGMouseAdapter() {
@Override
public void mouseClicked(MouseEvent e, SGNode n) {
Animation a = (Animation) n.getAttribute("startAnim");
a.start();
}
};
We do similarly for the reverse animation for the application (I'll skip that code for brevity and added surprise and excitement). TransformComposer
Chris might prefer that I not mention this detail, since the final API for the scene
graph will hopefully make this step irrelevant, but for now the only way to animate
transforms such as the moves and scales shown above is for the animation engine
to know how to interpolate static {
Composer.register(AffineTransform.class, TransformComposer.class);
}
I won't go into the details of RuntimeThat's mostly it. If you run the application and click on the icons you can see the fading, moving, and scaling animations all working together to show a nice, smooth transition between the menu and application screens. Of course, in demos enough is never enough. So we decided to put in one more element for fun. In the Apple video, you'll notice that many of the demos they show are run by this disembodied hand. It could be the Hand of God, but I don't think that Steve was in the video. Besides, the hand isn't wearing a black turtleneck.
It seemed like our demo needed an element like that, so we created
the Handy CursorCustom cursors are fairly easy in Java, but they are also fairly limited. In particular, your cursor image is limited to 32x32, which doesn't really give us the effect we were looking for. I want a hand, not a hand-shaped wart. We need a friggin' huge cursor. In a traditional Swing application, we could manage this using the glass pane, displaying an arbitrary image in that overlay on top of the application GUI. But the scene graph makes this even easier; we just need a shape node. First things first: we need to manage the real Swing cursor. Since we cannot use the actual cursor as our hand, we will instead make the real cursor invisible with the following code, so that if we can't make the cursor do what we really want, we can at least get it out of the way: BufferedImage emptyImage = new BufferedImage(32, 32,
BufferedImage.TYPE_INT_ARGB);
invisibleCursor = Toolkit.getDefaultToolkit().
createCustomCursor(emptyImage, new Point(0, 0), "empty");
Next, we will create our handNode = new HandCursor(rootNode);
addMouseMotionListener(handNode);
I won't show the entire handNode = new SGImage();
handNode.setImage(largerHand);
handNode.setVisible(false);
handTransform = SGTransform.createTranslation(0, 0, handNode);
rootNode.add(handTransform);
When the application frame detects that the key "h" has been typed, it makes the default cursor invisible and the hand cursor visible with the following: setCursor(invisibleCursor);
handNode.setVisible(true);
Now, all we have to do is track the mouse position and display the hand node appropriately: public void mouseMoved(MouseEvent me) {
mouseX = me.getX();
mouseY = me.getY();
handTransform.setTranslation(mouseX - 160, mouseY - 5);
}
(where the hard-coded numbers in
| ||
|
|