Interfaces as capabilities
The package is written with one particular concept, throughout:
Types represent capabilities. If the developer has a value of a
given type, then the developer can perform all of the operations
associated with that type.
This is in direct contrast to the OpenGL
API, where the developer always has full access to all API functions,
and individual functions (or combinations of parameters to functions)
may not work based on what the implementation supports. The emphasis
is on isolating the correct operations and
statically preventing incorrect operations from occurring.
Types
The
jcanephora
package wraps all
conceptually distinct objects in new types and provides sane and
unsurprising functions to construct them. This prevents large
classes of bugs from occurring, at compile time. It also allows
the interfaces to be self-documenting. Consider the following
C function prototype:
What does the function do? It could generate a set of textures
(glGenTextures), or it could
generate a set of new buffer names
(glGenBuffers). Without carefully
reading the documentation, the required parameters to the function
are certainly not obvious from the type alone. Assuming the developer
has read the documentation and worked out that the correct way to
call the function is with the following sequence:
The developer now has a value of type
GLuint. Given that many functions in
the OpenGL API also take values of
GLuint,
and those values have nothing to do with textures, that's a huge
number of potential bugs that cannot be prevented by the compiler.
The developer now wants to allocate a new RGBA texture. The
types of the glTexImage2D
and glTexParameteri functions
are, quite frankly, insane. Almost no developer manages to use these
functions successfully without trial and error and, naturally, all
errors are runtime errors. Eventually, through some painful
experimentation, the developer might stumble onto the following
sequence of calls:
The types of the parameters to all of the functions mentioned allow
the developer to pass in practically anything. Only a very small
subset of the possible parameters are actually valid. An even
smaller subset of the possible parameters actually work across
different versions of OpenGL!
Now consider the
following jcanephora function:
Even without the names of the function and the parameters,
the type above make it quite clear that the function returns a
JCGLTexture2DType, which is somehow
derived from the arguments to the function. It's a reasonable
assumption to make that the function allocates a new texture.
The wrapping modes on the S and
T axis will presumably be
derived from the parameters of type
JCGLTextureWrapS and
JCGLTextureWrapT respectively. The
minification and magnification filters will presumably be derived
from the parameters of type
JCGLTextureFilterMinification and
JCGLTextureFilterMagnification,
respectively. The programmer is statically prevented from mixing
up the two axes, or the two filter domains. The
JCGLTextureFormat parameter is
obviously intended to specify the format of the texture. Any
parameter familiar with OpenGL texturing will be aware that any
operations involving textures requires access to a
texture unit, and the
JCGLTextureUnitType parameter
above presumably specifies the unit that will be used.
The two parameters of type long
presumably correspond to the width and height of the resulting
texture, but the types are not descriptive enough to say for sure.
Unfortunately, Java lacks a way to make existing arithmetic types
incompatible without sacrificing efficiency
. In this case, the two parameters do indeed
correspond to the width and height, in that order, and the
jcanephora package uses this
order consistently throughout.
The extremely descriptive type given for the function, and the
presence of declared (but unchecked) exceptions are direct contributions
towards the goals of
type safety
and
conspicuity of errors.
It is very difficult for the programmer to use this function
incorrectly: the type system guarantees correct usage.