Skip to content

Commit

Permalink
Add HybridBuffer and use it to handle 8/16-bit conversion in LXLayere…
Browse files Browse the repository at this point in the history
…dComponent.
  • Loading branch information
zestyping committed Apr 26, 2018
1 parent 18e21e3 commit 325608f
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 34 deletions.
5 changes: 5 additions & 0 deletions src/heronarts/lx/LXBuffer16.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package heronarts.lx;

public interface LXBuffer16 {
public long[] getArray();
}
4 changes: 2 additions & 2 deletions src/heronarts/lx/LXLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ protected LXLayer(LX lx) {
super(lx);
}

protected LXLayer(LX lx, LXDeviceComponent buffer) {
super(lx, buffer);
protected LXLayer(LX lx, LXDeviceComponent component) {
super(lx, component);
}

@Override
Expand Down
225 changes: 193 additions & 32 deletions src/heronarts/lx/LXLayeredComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package heronarts.lx;

import heronarts.lx.color.LXColor;
import heronarts.lx.color.LXColor16;
import heronarts.lx.color.LXPalette;
import heronarts.lx.model.LXFixture;
import heronarts.lx.model.LXPoint;
Expand All @@ -30,72 +31,167 @@
import java.util.List;

/**
* Base class for system components that run in the engine, which have common
* attributes, such as parameters, modulators, and layers. For instance,
* patterns, transitions, and effects are all LXComponents.
* Base class for system components that have a color buffer and run in the
* engine, with common attributes such as parameters, modulators, and layers.
* For instance, patterns, transitions, and effects are all LXLayeredComponents.
* Subclasses do their work mainly by implementing onLoop() to write into the
* color buffer.
*
* LXLayeredComponents have both an 8-bit color buffer (buffer) and a 16-bit
* color buffer (buffer16). In subclasses marked with LXLayeredComponent.Uses16,
* onLoop() should internally write only to the 16-bit buffer (whose contents
* will be automatically converted to 8-bit colors as needed). In subclasses
* not marked with Uses16, onLoop() should write only to the 8-bit buffer
* (whose contents will be automatically converted to 16-bit colors as needed).
*
* LXLayeredComponent subclasses can be marked as LXLayeredComponent.Buffered
* (which means they own their own buffers), or not (which means they operate
* on external buffers passed in via setBuffer()).
*
* For subclasses marked Buffered:
* Internal API:
* The implementation of onLoop() should write colors into the
* appropriate buffer (buffer16/colors16 for a Uses16 class, or
* buffer/colors otherwise).
* External API:
* getBuffer() returns the 8-bit colors (converting from 16 bits if needed).
* getBuffer16() returns the 16-bit colors (converting from 8 bits if needed).
* setBuffer() and setBuffer16() are illegal to call.
*
* For subclasses not marked Buffered:
* Internal API:
* The implementation of onLoop() should read and write the contents
* of the appropriate buffer (buffer16/colors16 for a Uses16 class,
* or buffer/colors otherwise).
* External API:
* getBuffer() and getBuffer16() are the same as above.
* setBuffer() takes an 8-bit LXBuffer and makes it the place where
* onLoop()'s results will appear (in a Uses16 class, its contents
* will be converted to 16 bits before onLoop() and then back to
* 8 bits after; in a non-Uses16 class no conversion is needed).
* setBuffer16() takes a 16-bit LXBuffer16 and makes it the place where
* onLoop()'s results will appear (in a non-Uses16 class, its
* contents will be converted to 8 bits before onLoop() and then
* back to 16 bits after; in a Uses16 class no conversion is needed).
*/
public abstract class LXLayeredComponent extends LXModelComponent implements LXLoopTask {
/** Marker interface for subclasses that work on the 16-bit buffer (buffer16). */
public interface Uses16 {}

/**
* Marker interface for instances which own their own buffer.
*/
/** Marker interface for subclasses that want to own their own buffers. */
public interface Buffered {}

public final Timer timer = constructTimer();

protected final LX lx;

private LXBuffer buffer = null;
// In a Uses16 class, buffer is nullable and buffer16 is non-nullable.
// In a non-Uses16 class, buffer is non-nullable and buffer16 is nullable.
private LXBuffer buffer = null; // 8-bit color buffer
private LXBuffer16 buffer16 = null; // 16-bit color buffer

// These can be used as convenient aliases for buffer.getArray() and buffer16.getArray()
// in subclass implementations of onLoop() and run().
protected int[] colors = null;
protected long[] colors16 = null;

// This tracks whether buffer and buffer16 have matching data, to avoid
// unnecessarily repeating a data conversion. Subclasses should never
// modify buffers outside of the abstract methods onLoop() and afterLayers().
private boolean buffersInSync = false;

private final List<LXLayer> mutableLayers = new ArrayList<LXLayer>();
protected final List<LXLayer> layers = Collections.unmodifiableList(mutableLayers);

protected final LXPalette palette;

protected LXLayeredComponent(LX lx) {
this(lx, (LXBuffer) null);
super(lx);
this.lx = lx;
palette = lx.palette;
if (this instanceof Buffered) {
if (this instanceof Uses16) {
buffer16 = new ModelBuffer16(lx);
colors16 = buffer16.getArray();
} else {
buffer = new ModelBuffer(lx);
colors = buffer.getArray();
}
}
}

protected LXLayeredComponent(LX lx, LXDeviceComponent component) {
this(lx, component.getBuffer());
this(lx);
setBuffer(component);
}

protected LXLayeredComponent(LX lx, LXBuffer buffer) {
super(lx);
if (this instanceof Buffered) {
if (buffer != null) {
throw new IllegalArgumentException("Cannot pass existing buffer to LXLayeredComponent.Buffered, has its own");
}
buffer = new ModelBuffer(lx);
}
this.lx = lx;
this.palette = lx.palette;
if (buffer != null) {
this.buffer = buffer;
this.colors = buffer.getArray();
}
protected LXLayeredComponent(LX lx, LXBuffer externalBuffer) {
this(lx);
setBuffer(externalBuffer);
}

protected LXLayeredComponent(LX lx, LXBuffer16 externalBuffer16) {
this(lx);
setBuffer16(externalBuffer16);
}

/** Gets the 8-bit color buffer (performing conversions if necessary). */
protected LXBuffer getBuffer() {
return this.buffer;
if (this instanceof Uses16) {
syncBufferFromBuffer16();
}
return buffer;
}

/** Gets the 16-bit color buffer (performing conversions if necessary). */
protected LXBuffer16 getBuffer16() {
if (!(this instanceof Uses16)) {
syncBuffer16FromBuffer();
}
return buffer16;
}

/** Gets the array from the 8-bit color buffer (performing conversions if necessary). */
public int[] getColors() {
return getBuffer().getArray();
}

/** Gets the array from the 16-bit color buffer (performing conversions if necessary). */
public long[] getColors16() {
return getBuffer16().getArray();
}

/** Sets the buffer of another component as the buffer to read from and write to. */
protected LXLayeredComponent setBuffer(LXDeviceComponent component) {
if (component instanceof Uses16) {
return setBuffer16(component.getBuffer16());
} else {
return setBuffer(component.getBuffer());
}
}

/** Sets an external 8-bit color buffer as the buffer to read from and write to. */
protected LXLayeredComponent setBuffer(LXBuffer externalBuffer) {
if (this instanceof Buffered) {
throw new UnsupportedOperationException("Cannot setBuffer on LXLayerdComponent.Buffered, owns its own buffer");
throw new UnsupportedOperationException("Cannot setBuffer on a LXLayeredComponent.Buffered, owns its own buffer");
}
return setBuffer(component.getBuffer());
buffer = externalBuffer;
colors = externalBuffer.getArray();
if (this instanceof Uses16) {
syncBuffer16FromBuffer(); // make data visible to a 16-bit onLoop
}
return this;
}

protected LXLayeredComponent setBuffer(LXBuffer buffer) {
this.buffer = buffer;
this.colors = buffer.getArray();
/** Sets an external 16-bit color buffer as the buffer to read from and write to. */
protected LXLayeredComponent setBuffer16(LXBuffer16 externalBuffer16) {
if (this instanceof Buffered) {
throw new UnsupportedOperationException("Cannot setBuffer on a LXLayeredComponent.Buffered, owns its own buffer");
}
buffer16 = externalBuffer16;
colors16 = externalBuffer16.getArray();
if (!(this instanceof Uses16)) {
syncBufferFromBuffer16(); // make data visible to an 8-bit onLoop
}
return this;
}

Expand All @@ -107,19 +203,38 @@ public void loop(double deltaMs) {
// reference. Even if a doofus assigns colors to something else, we'll reset it
// here on each pass of the loop. Better than subclasses having to call getColors()
// all the time.
this.colors = this.buffer.getArray();
colors = buffer != null ? buffer.getArray() : null;
colors16 = buffer16 != null ? buffer16.getArray() : null;

super.loop(deltaMs);
onLoop(deltaMs);
buffersInSync = false;

for (LXLayer layer : this.mutableLayers) {
layer.setBuffer(this.buffer);
if (this instanceof Uses16) {
layer.setBuffer16(buffer16);
} else {
layer.setBuffer(buffer);
}

// TODO(mcslee): is this best here or should it be in addLayer?
layer.setModel(this.model);

layer.loop(deltaMs);
}
afterLayers(deltaMs);
buffersInSync = false;

if (!(this instanceof Buffered)) {
// The buffers are external; we need to make the output from onLoop() and
// afterLayers() visible in the external buffers, converting if needed.
if (this instanceof Uses16 && buffer != null) {
syncBufferFromBuffer16();
}
if (!(this instanceof Uses16) && buffer16 != null) {
syncBuffer16FromBuffer();
}
}

this.timer.loopNanos = System.nanoTime() - loopStart;
}
Expand Down Expand Up @@ -156,6 +271,52 @@ public void dispose() {
super.dispose();
}

/** Ensures that buffer contains a copy of buffer16's contents. */
protected void syncBufferFromBuffer16() {
if (buffer == null) {
buffer = new ModelBuffer(lx);
}
if (!buffersInSync) {
LXColor16.longsToInts(buffer16.getArray(), buffer.getArray());
buffersInSync = true;
}
}

/** Ensures that buffer16 contains a copy of buffer's contents. */
protected void syncBuffer16FromBuffer() {
if (buffer16 == null) {
buffer16 = new ModelBuffer16(lx);
}
if (!buffersInSync) {
LXColor.intsToLongs(buffer.getArray(), buffer16.getArray());
buffersInSync = true;
}
}

// NOTE(ping): Most of the utility routines below are rarely used,
// and rarely or never chained. We won't reimplement them all for
// 16-bit color, just setColor and setColors.

/** Sets the 16-bit color of a single point. */
protected void setColor16(int i, long c) {
colors16[i] = c;
}

/** Sets the 16-bit color of all points. */
protected void setColors16(int c) {
for (int i = 0; i < colors16.length; i++) {
colors16[i] = c;
}
}

/** Sets the 16-bit color of all points in a fixture. */
protected void setColor16(LXFixture f, long c) {
for (LXPoint p : f.getPoints()) {
colors16[p.index] = c;
}
}


/**
* Sets the color of point i
*
Expand Down
26 changes: 26 additions & 0 deletions src/heronarts/lx/ModelBuffer16.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package heronarts.lx;

import heronarts.lx.model.LXModel;

public class ModelBuffer16 implements LXBuffer16 {
private long[] array;

public ModelBuffer16(LX lx) {
initArray(lx.model);

lx.addListener(new LX.Listener() {
@Override
public void modelChanged(LX lx, LXModel model) {
initArray(model);
}
});
}

private void initArray(LXModel model) {
this.array = new long[model.size]; // initialized to 0 by Java
}

public long[] getArray() {
return this.array;
}
}

0 comments on commit 325608f

Please sign in to comment.