Skip to content

Commit

Permalink
Merge pull request #33 from heronarts/dev
Browse files Browse the repository at this point in the history
Merge release 0.2.1
  • Loading branch information
mcslee authored Oct 22, 2020
2 parents 8a83c80 + f926c8a commit e3d0d11
Show file tree
Hide file tree
Showing 86 changed files with 5,365 additions and 1,718 deletions.
6 changes: 6 additions & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
0.2.1
October 22, 2020
- COVID lockdown
- Improvements to fixture/structure/JSON system
- Many tweaks and improvements, new patterns/effects

0.2.0
May 28, 2020
- Lots of API cleanups
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>heronarts</groupId>
<artifactId>lx</artifactId>
<version>0.2.0</version>
<version>0.2.1</version>
<packaging>jar</packaging>

<properties>
Expand Down
117 changes: 101 additions & 16 deletions src/main/java/heronarts/lx/LX.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
import heronarts.lx.parameter.MutableParameter;
import heronarts.lx.parameter.StringParameter;
import heronarts.lx.pattern.LXPattern;
import heronarts.lx.pattern.test.TestPattern;
import heronarts.lx.pattern.color.SolidPattern;
import heronarts.lx.scheduler.LXScheduler;
import heronarts.lx.structure.LXFixture;
import heronarts.lx.structure.LXStructure;

Expand Down Expand Up @@ -66,7 +67,7 @@
*/
public class LX {

public static final String VERSION = "0.2.0";
public static final String VERSION = "0.2.1";

public static class InstantiationException extends Exception {

Expand All @@ -78,6 +79,12 @@ protected InstantiationException(Exception underlying) {

}

public interface Permissions {
public boolean canSave();

public int getMaxPoints();
}

public static class Flags {
/**
* Sometimes we need to know if we are P3LX, but we don't want LX library to have
Expand All @@ -98,7 +105,8 @@ public static enum Media {
FIXTURES("Fixtures"),
PROJECTS("Projects"),
MODELS("Models"),
LOGS("Logs");
LOGS("Logs"),
DELETED("Deleted");

private final String dirName;

Expand All @@ -109,6 +117,10 @@ private Media(String dirName) {
public String getDirName() {
return this.dirName;
}

private boolean isBootstrap() {
return (this != DELETED);
}
}

public static class Error {
Expand Down Expand Up @@ -156,6 +168,10 @@ public static String version() {
public static final double HALF_PI = Math.PI / 2.;
public static final double TWO_PI = Math.PI * 2.;

public static final float PIf = (float) Math.PI;
public static final float HALF_PIf = (float) (Math.PI / 2.);
public static final float TWO_PIf = (float) (Math.PI * 2.);

public static class InitProfiler {
private long lastTime;

Expand Down Expand Up @@ -184,7 +200,27 @@ public static void logInitProfiler() {
* Listener for top-level events
*/
public interface Listener {
/**
* Fired whenever a new model instance is set on this LX instance. The
* passed model is an entirely new object that has not been set before.
*
* @param lx LX instance
* @param model Model instance
*/
default public void modelChanged(LX lx, LXModel model) {}

/**
* Fired when the generation of a model has been changed. This is the same
* model instance that has already been set on LX, but it has been modified.
* This is also fired the very first time a model is set (e.g. generation 0
* for the model). Listeners that wish to take generic action based upon any new
* model geometry, whether it's an existing or new model, may listen to just
* this method.
*
* @param lx LX instance
* @param model model instance
*/
default public void modelGenerationChanged(LX lx, LXModel model) {}
}

private final List<Listener> listeners = new ArrayList<Listener>();
Expand Down Expand Up @@ -215,6 +251,19 @@ enum Change {
*/
public final Flags flags;

/**
* Permissions
*/
protected Permissions permissions = new Permissions() {
public boolean canSave() {
return true;
}

public int getMaxPoints() {
return -1;
}
};

/**
* Error stack
*/
Expand Down Expand Up @@ -264,6 +313,11 @@ enum Change {
*/
public final LXRegistry registry;

/**
* The project scheduler
*/
public final LXScheduler scheduler;

/**
* Creates an LX instance with no nodes.
*/
Expand Down Expand Up @@ -296,14 +350,30 @@ protected LX(Flags flags, LXModel model) {
} else {
this.model = model;
}
this.structure.setModelListener((newModel) -> { setModel(newModel); });
this.structure.setModelListener(new LXStructure.ModelListener() {
public void structureChanged(LXModel model) {
setModel(model);
}
public void structureGenerationChanged(LXModel model) {
for (Listener listener : listeners) {
listener.modelGenerationChanged(LX.this, model);
}
}
});
LX.initProfiler.log("Model");

// Custom content loader
this.registry = instantiateRegistry(this);
this.registry.initialize();
LX.initProfiler.log("Registry");

// Load the global preferences before plugin initialization
this.preferences = new LXPreferences(this);
this.preferences.load();

// Scheduler
this.scheduler = new LXScheduler(this);

// Construct the engine
this.engine = new LXEngine(this);
this.command = new LXCommandEngine(this);
Expand All @@ -312,14 +382,6 @@ protected LX(Flags flags, LXModel model) {
// Midi
this.engine.midi.initialize();

// Add a default channel
this.engine.mixer.addChannel(new LXPattern[] { new TestPattern(this) }).fader.setValue(1);
LX.initProfiler.log("Default Channel");

// Load the global preferences before plugin initialization
this.preferences = new LXPreferences(this);
this.preferences.load();

// Initialize plugins!
if (this.flags.initialize != null) {
this.flags.initialize.initialize(this);
Expand Down Expand Up @@ -351,6 +413,10 @@ protected void fail(Exception x) {
this.failure.setValue(message);
}

public Permissions getPermissions() {
return this.permissions;
}

public LX pushError(Exception exception) {
return pushError(new Error(exception));
}
Expand Down Expand Up @@ -465,6 +531,9 @@ private LX setModel(LXModel model) {
for (Listener listener : this.listeners) {
listener.modelChanged(this, model);
}
for (Listener listener : this.listeners) {
listener.modelGenerationChanged(this, model);
}

// Dispose of the old model after notifying listeners of model change
if (oldModel != null) {
Expand Down Expand Up @@ -750,6 +819,10 @@ public void saveProject() {
}

public void saveProject(File file) {
if (!this.permissions.canSave()) {
return;
}

JsonObject obj = new JsonObject();
obj.addProperty(KEY_VERSION, LX.VERSION);
obj.addProperty(KEY_TIMESTAMP, System.currentTimeMillis());
Expand Down Expand Up @@ -807,6 +880,10 @@ public void newProject() {
this.structure.load(this, new JsonObject());
}
this.engine.load(this, new JsonObject());

LXChannel channel = this.engine.mixer.addChannel(new LXPattern[] { new SolidPattern(this, 0xffff0000) });
channel.fader.setValue(1);

setProject(null, ProjectListener.Change.NEW);
});
}
Expand Down Expand Up @@ -841,6 +918,8 @@ public void openProject(File file) {
} catch (Exception x) {
LX.error(x, "Exception in openProject: " + x.getLocalizedMessage());
pushError(x, "Exception in openProject: " + x.getLocalizedMessage());
} finally {
this.componentRegistry.projectLoading = false;
}
}

Expand All @@ -854,6 +933,10 @@ public void setModelImportFlag(boolean modelImport) {
this.componentRegistry.modelImporting = modelImport;
}

public void setScheduleLoadingFlag(boolean scheduleLoading) {
this.componentRegistry.scheduleLoading = scheduleLoading;
}

protected final void confirmChangesSaved(String message, Runnable confirm) {
if (this.command.isDirty()) {
showConfirmUnsavedProjectDialog(message, confirm);
Expand Down Expand Up @@ -1053,10 +1136,12 @@ protected static void bootstrapMediaPath(Flags flags, String dirName) {
if (studioDir.isDirectory()) {
flags.mediaPath = studioDir.getPath();
for (LX.Media type : LX.Media.values()) {
File contentDir = new File(studioDir, type.getDirName());
if (!contentDir.exists()) {
LX.log("Creating directory: " + contentDir);
contentDir.mkdir();
if (type.isBootstrap()) {
File contentDir = new File(studioDir, type.getDirName());
if (!contentDir.exists()) {
LX.log("Creating directory: " + contentDir);
contentDir.mkdir();
}
}
}
} else {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/heronarts/lx/LXCategory.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
public static final String CORE = "Core";
public static final String FORM = "Form";
public static final String COLOR = "Color";
public static final String MIDI = "MIDI";
public static final String TEXTURE = "Texture";
public static final String TEST = "Test";
public static final String OTHER = "Other";
Expand Down
67 changes: 62 additions & 5 deletions src/main/java/heronarts/lx/LXClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,42 @@
*/
public class LXClassLoader extends URLClassLoader {

public static class Package {

final File jarFile;

public final String name;

private Throwable error = null;
private int numClasses = 0;

private Package(File jarFile) {
this.jarFile = jarFile;
String name = jarFile.getName();
if (name.endsWith(".jar")) {
name = name.substring(0, name.length() - ".jar".length());
}
this.name = name;
}

private void setError(Throwable error) {
this.error = error;
}

public int getNumClasses() {
return this.numClasses;
}

public boolean hasError() {
return this.error != null;
}

public Throwable getError() {
return this.error;
}

}

private final LX lx;

private final List<Class<?>> classes = new ArrayList<Class<?>>();
Expand Down Expand Up @@ -113,39 +149,60 @@ protected void dispose() {
}

private void loadJarFile(File file) {
LX.log("Loading custom content from: " + file);
LX.log("Loading package content from: " + file);
Package pack = new Package(file);
try (JarFile jarFile = new JarFile(file)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String fileName = entry.getName();
if (fileName.endsWith(".class")) {
loadClassEntry(jarFile, className(fileName).replaceAll("/", "\\."));
if (fileName.endsWith(".lxf")) {
// TODO(mcslee): load fixtures from a JAR!
} else if (fileName.endsWith(".lxm")) {
// TODO(mcslee): load models from a JAR!
} else if (fileName.endsWith(".lxp")) {
// TODO(mcslee): load projects from a JAR!
} if (fileName.endsWith(".class")) {
loadClassEntry(pack, jarFile, className(fileName).replaceAll("/", "\\."));
}
}
} catch (IOException iox) {
LX.error(iox, "IOException unpacking JAR file " + file + " - " + iox.getLocalizedMessage());
pack.setError(iox);
} catch (Exception | Error e) {
LX.error(e, "Unhandled exception loading JAR file " + file + " - " + e.getLocalizedMessage());
pack.setError(e);
}

this.lx.registry.addPackage(pack);
}

private static String className(String fileName) {
return fileName.substring(0, fileName.length() - ".class".length());
}

private void loadClassEntry(JarFile jarFile, String className) {
private void loadClassEntry(Package pack, JarFile jarFile, String className) {
try {
// This might be slightly slower, but just let URL loader find it...
// Let's not re-invent the wheel on parsing JAR files and all that
// Let's not re-invent the wheel on parsing JAR files and all that.
Class<?> clz = loadClass(className, false);

// TODO(mcslee): there must be some better way of checking this explicitly,
// without instantiating the class, but more clear than getSimpleName()

// Okay, we loaded the class. But can we actually operate on it? Let's try
// to get the name of this class to ensure it's not going to bork things
// later due to unresolved dependencies that will throw NoClassDefFoundError
clz.getSimpleName();

// Register all public, non-abstract components that we discover
int modifiers = clz.getModifiers();
if (Modifier.isPublic(modifiers) && !Modifier.isAbstract(modifiers)) {
++pack.numClasses;
this.classes.add(clz);
this.lx.registry.addClass(clz);
}

} catch (ClassNotFoundException cnfx) {
LX.error(cnfx, "Class not actually found, expected in JAR file: " + className + " " + jarFile.getName());
} catch (Exception x) {
Expand Down
Loading

0 comments on commit e3d0d11

Please sign in to comment.