Am I using a feature of Java 8 or misusing it?
Refer the code and explanation below to know as to why it was chosen to be like this.
public interface Drawable {
public void compileProgram();
public Program getProgram();
default public boolean isTessellated() {
return false;
}
default public boolean isInstanced() {
return false;
}
default public int getInstancesCount() {
return 0;
}
public int getDataSize();
public FloatBuffer putData(final FloatBuffer dataBuffer);
public int getDataMode();
public boolean isShadowReceiver();
public boolean isShadowCaster(); //TODO use for AABB calculations
default public void drawDepthPass(final int offset, final Program depthNormalProgram, final Program depthTessellationProgram) {
Program depthProgram = (isTessellated()) ? depthTessellationProgram : depthNormalProgram;
if (isInstanced()) {
depthProgram.use().drawArraysInstanced(getDataMode(), offset, getDataSize(), getInstancesCount());
}
else {
depthProgram.use().drawArrays(getDataMode(), offset, getDataSize());
}
}
default public void draw(final int offset) {
if (isInstanced()) {
getProgram().use().drawArraysInstanced(getDataMode(), offset, getDataSize(), getInstancesCount());
}
else {
getProgram().use().drawArrays(getDataMode(), offset, getDataSize());
}
}
default public void delete() {
getProgram().delete();
}
public static int countDataSize(final Collection<Drawable> drawables) {
return drawables.stream()
.mapToInt(Drawable::getDataSize)
.sum();
}
public static FloatBuffer putAllData(final List<Drawable> drawables) {
FloatBuffer dataBuffer = BufferUtils.createFloatBuffer(countDataSize(drawables) * 3);
drawables.stream().forEachOrdered(drawable -> drawable.putData(dataBuffer));
return (FloatBuffer)dataBuffer.clear();
}
public static void drawAllDepthPass(final List<Drawable> drawables, final Program depthNormalProgram, final Program depthTessellationProgram) {
int offset = 0;
for (Drawable drawable : drawables) {
if (drawable.isShadowReceiver()) {
drawable.drawDepthPass(offset, depthNormalProgram, depthTessellationProgram);
}
offset += drawable.getDataSize(); //TODO count offset only if not shadow receiver?
}
}
public static void drawAll(final List<Drawable> drawables) {
int offset = 0;
for (Drawable drawable : drawables) {
drawable.draw(offset);
offset += drawable.getDataSize();
}
}
public static void deleteAll(final List<Drawable> drawables) {
drawables.stream().forEach(Drawable::delete);
}
}
public interface TessellatedDrawable extends Drawable {
@Override
default public boolean isTessellated() {
return true;
}
}
public interface InstancedDrawable extends Drawable {
@Override
default public boolean isInstanced() {
return true;
}
@Override
public int getInstancesCount();
}
public class Box implements TessellatedDrawable, InstancedDrawable {
//<editor-fold defaultstate="collapsed" desc="keep-imports">
static {
int KEEP_LWJGL_IMPORTS = GL_2_BYTES | GL_ALIASED_LINE_WIDTH_RANGE | GL_ACTIVE_TEXTURE | GL_BLEND_COLOR | GL_ARRAY_BUFFER | GL_ACTIVE_ATTRIBUTE_MAX_LENGTH | GL_COMPRESSED_SLUMINANCE | GL_ALPHA_INTEGER | GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH | GL_ALREADY_SIGNALED | GL_ANY_SAMPLES_PASSED | GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH | GL_ACTIVE_PROGRAM | GL_ACTIVE_ATOMIC_COUNTER_BUFFERS | GL_ACTIVE_RESOURCES | GL_BUFFER_IMMUTABLE_STORAGE;
int KEEP_OWN_IMPORTS = UNIFORM_PROJECTION_MATRIX.getLocation() | VS_POSITION.getLocation();
}
//</editor-fold>
private FloatBuffer data;
private Program program;
private final float width, height, depth;
public Box(final float width, final float height, final float depth) {
this.width = width;
this.height = height;
this.depth = depth;
data = generateBox();
data.clear();
}
@Override
public void compileProgram() {
program = new Program(
new VertexShader("data/shaders/box.vs.glsl").compile(),
new FragmentShader("data/shaders/box.fs.glsl").compile()
).compile().usingUniforms(
UNIFORM_MODEL_MATRIX,
UNIFORM_VIEW_MATRIX,
UNIFORM_PROJECTION_MATRIX,
UNIFORM_SHADOW_MATRIX
);
}
@Override
public int getInstancesCount() {
return 100;
}
@Override
public Program getProgram() {
return program;
}
@Override
public int getDataSize() {
return 6 * 6;
}
@Override
public FloatBuffer putData(final FloatBuffer dataBuffer) {
FloatBuffer returnData = dataBuffer.put(data);
data.clear(); //clear to reset data state
return returnData;
}
@Override
public int getDataMode() {
return GL_TRIANGLES;
}
@Override
public boolean isShadowReceiver() {
return true;
}
@Override
public boolean isShadowCaster() {
return true;
}
private FloatBuffer generateBox() {
FloatBuffer boxData = BufferUtils.createFloatBuffer(6 * 6 * 3);
//put data into boxData
return (FloatBuffer)boxData.clear();
}
}
First the steps on how I came to this code:
I started with the
Drawable
interface and each implementation having its owndrawDepthPass
,draw
anddelete
methods.Refactoring
delete
to adefault
method was easy, trivial and should not be wrong.However to be able to refactor
drawDepthPass
anddraw
I needed access to whether aDrawable
was tesselated and/or instanced, so I added the public (non-default) methodsisTessellated()
,isInstanced()
andgetInstancesCount()
.Then I figured out it would be slightly cumbersome, as we programmers are lazy, to implement them in every
Drawable
.As a consequence I added the
default
methods toDrawable
, giving the behaviour of the most basicDrawable
.Then I figured that I am still lazy and do not want to manually implement it for the tessellated and instanced variants eithere.
So I created
TessellatedDrawable
andInstancedDrawable
that providedefault
isTessellated()
andisInstanced()
respectively. And inInstancedDrawable
I revoked thedefault
implementation ofgetInstancesCount()
.
As a result I can have the following:
- Normal
Drawable
:public class A implements Drawable
- Tessellated
Drawable
:public class A implements TessellatedDrawable
- Instanced
Drawable
:public class A implements InstancedDrawable
- Tessellated and instanced
Drawable
:public class A implements InstancedDrawable, TessellatedDrawable
.
Just to ensure you, this all compiles and runs fine, the implements InstancedDrawable, TessellatedDrawable
gets handled perfectly by Java 8 as there is nowhere ever ambiguity on from which interface the functionality should come.
Now onto my own little OOP design assessment:
- Every
Drawable
is in fact aDrawable
, soCollection<Drawable>
will not break. - It is possible to group all
TessellatedDrawable
and/orInstancedDrawable
, irrelevant of how exactly it is implement.
Other thoughts I had:
Use a more conventional, layered approach, however I disregarded that as it would end up in:
abstract class AbstractDrawable
class Drawable extends AbstractDrawable
class TessellatedDrawable extends AbstractDrawable
class InstancedDrawable extends AbstractDrawable
class InstancedTessellatedDrawable extends AbstractDrawable
I have also considered a Builder Pattern, however that is a pattern to be used when you are creating a lot of unique instances of a certain object, and that is not what we are doing here, neither is this about the constructor of the object.
So the first and final question was: Am I using a feature of Java 8 or misusing it?