Sunday, July 26, 2009

Lessons learned from IPod Development

A timeline with a new library and language


Learning a language like Objective-C and the iPhone SDK is very time consuming. Doing this at the same time where a professor is expecting you to produce a vague product (read: no concrete requirements), and expect to be confused. That's not to say it wasn't a very effective learning experience.

Challenges


Learning a library and developing a product at the same time is not an efficient process. Your designs are colored by your understanding of the previous libraries and languages you have worked with--not the current one you need to develop with.

This boils down into two categories of challenges:

Identifying constraints


What can the library not do? Is there functionality from other libraries I would use and isn't available from the current library? What alternatives are there with the library I'm using now?

Identifying capabilities


What things can the library make easier for me? These are much harder to discover as they lurk in hundreds of html docs or deep in information-light tutorials. Ofter there are constraints which are only imaginary because the library offers the functionality you want, you just don't know about it yet.

Inaccurate (Imaginary) Constraints and Capabilities


Working in an unfamiliar language with an unfamiliar library easily lends itself to inaccurate assessments of the tool's constraints or capabilities. There might not even be an analogous construct, such as moving from Python to C and expecting duck typing. The tool might offer new constructs to use such as going from C++ to Ruby and not knowing about mixins, an example of inaccurate constraints.

Since the cost of changes in software grows exponentially as the project progresses, continuing under flawed assumptions of constraints and capabilities often makes it cheaper in time and effort to redesign and rebuild from scratch.

Conclusions


Know when to start over and redesign


This is may be the more painful of the two lessons. Abandoning a program and retooling it from scratch is a hard prospect when facing approaching deadlines. This can be important to realize during design and further emphasize the need for clearly defined subsystems and packages and components with minimal interdependencies as a levee against cascading changes in a redesign.

Abstractions and a levelizable design will act as firebreaks when you rip out part of the system after finding several capabilities you didn't know the library had.

Writing for a new library is liberating when you expect to throw your code away, but don't get careless. Rewriting something is a lot easier when it is hidden behind an abstraction and the fewer dependencies on a component means less code to change if that component gets modified significantly.

Rewrite may not mean "full rewrite" if you watch and reduce the number of places you're depending on that new library. Of course you're sunk if the new tool is a new language.

Learn your environment


Learning about the environment is especially important if you are to make a product out of it quickly from management. You should treat the task of learning about your environment as requirements analysis for the NEXT iteration of th eprogram. This goes back to the previous conclusion, that you should expect to do a redesign.

Trying out things in a test (or sandbox, whatever term you prefer) is especially important to learn how to apply new concepts in the domain. Using a separate project for this work is key so you don't screw up prototype code with experimentation. To a certain extent the current prototype is experimentation code, but it is something to show your boss and should be treated as production-level code.

The faster you learn the capabilities of the new tool, the fewer redesigns you will need to do because of a lack of understanding about the project's domain. As your progress there will be less work needed to identify constraints and capabilities of the new tool. It is suggested that you spend much more time in the beginning doing many small sandbox projects to learn about various aspects of the environment, before you begin any project level code.

More work on the front end of the project in sandbox work means less program guts to destroy and better assumptions when work starts on the real program. If a task is annoying or common to deal with, you might be operating under undiscovered capabilities of the tool.

Wednesday, March 11, 2009

Drawing a Rosette



Building the Rosette


Above is a picture of a rosette with 40 points. To make a rosette, create a set of equidistant points around a circle and then connect every point to every other point.

Candidate Points


First, we need to compute the rosette points by making points equally spaced around a circle.

struct GLFloatPoint{GLfloat x, y;};

// Compute the candidate points, by equally
// spacing them around a circle.
GLFloatPoint* points = new GLFloatPoint[numPoints];
float angleStep = (3.14159f * 2.0f / numPoints);
float currentAngle = 0.0f;
for(unsigned int i=0; i<numPoints; i++)
{
points[i].x = radius*cos(currentAngle);
points[i].y = radius*sin(currentAngle);

currentAngle += angleStep;
}

Connecting Points


If you connect every point to every other one, you end up with duplicate edges between vertices.

How do you get not create duplicate edges in the simplest possible way? Once you connect a certain point to all others, keep it out of the list of points to connect to. This can be done using two loops. The outer loop tracks the vertex we are connecting from, and in the inner loop tracks the vertex to which we are connecting.

unsigned int index = 0;
// The vertex we are connecting from.
for(unsigned int j=0; j<numPoints; j++)
{
// Connect to all other points.
// Lines have an origin and end point.
for(unsigned int k=j+1; k<numPoints; k++)
{
rosettePoints[index].x = points[j].x;
rosettePoints[index].y = points[j].y;
index++;
rosettePoints[index].x = points[k].x;
rosettePoints[index].y = points[k].y;
index++;
}
}


Drawing


Vertex Arrays


Using vertex arrays, we can build the entire rosette at startup, and then can send all the vertices to be drawn at once. Using an array of GLFloatPoint, causes the x's and y's in the structs to align in memory just as if they had been put into an array of GLfloat.

Conceptually,

/* Structs */
GLFloatPoint A, B, C;
GLFloatPoint pointArray[] = {A, B, C}
/* Manually */
GLfloat floatArray[] = {A.x,A.y,
B.x,B.y, C.x,C.y}

// You need to cast the pointers for this to work,
// but this is why I miss pointers in Java/Ruby.
GLFloatPoint* pointPtr = (GLFloatPoint*)floatArray;
pointPtr[0].x = // A.x
pointPtr[0].y = // A.y
pointPtr[1].x = // B.x

GLfloat* floatPtr = (GLfloat*)pointArray;
floatPtr[0] = // A.x
floatPtr[1] = // A.y
floatPtr[2] = // B.x
floatPtr[3] = // B.y


Struct Pointer Magic


In short, we can merrily use the array of GLFloatPoint in place of a big array of floats. This makes it easier to figure out where you are in the array quickly, since incrementing a GLFloatPoint* in a loop will move you forward one point. Also, you can use array notation, point[10], for the 11'th point, for instance.


// Allow us to use vertex arrays
glEnableClientState(GL_VERTEX_ARRAY);

// Set the array of points to use
glVertexPointer(2, GL_FLOAT, 0, rosettePoints);

// Render lines from the array vertices.
// rosetteSize = (numPoints-1)*numPoints/2*2.
// This comes from connecting every point to
// every other point without duplicates.
// The "*2" comes from two vertices per line.
glDrawArrays(GL_LINES, 0, rosetteSize);