getNodes() {
+ return nodes;
+ }
+
+ public PVector getNode(int id) {
+ return nodes.get(id);
+ }
+
+ public int size() {
+ return nodes.size();
+ }
+}
diff --git a/examples/PathingBoids/ProcessingPDE/ProcessingPDE.pde b/examples/PathingBoids/ProcessingPDE/ProcessingPDE.pde
new file mode 100644
index 0000000..7d705cd
--- /dev/null
+++ b/examples/PathingBoids/ProcessingPDE/ProcessingPDE.pde
@@ -0,0 +1,41 @@
+import pthreading.*;
+
+PThreadManager boidManager;
+
+Path boidPath;
+
+public void setup() {
+
+ size(1280, 720);
+
+ textSize(20);
+
+ colorMode(PApplet.HSB, 360, 100, 100);
+
+ boidPath = new Path(this);
+
+ PThreadManager mangerForPath = new PThreadManager(this);
+ mangerForPath.addThread(boidPath);
+ mangerForPath.bindDraw();
+
+ boidManager = new PThreadManager(this);
+ boidManager.addThread(BoidRunner.class, 4, 60, 3000, boidPath);
+}
+
+public void draw() {
+ fill(0);
+ rect(0, 0, 550, 80);
+ fill(0, 15);
+ rect(0, 0, width, height);
+ boidManager.draw();
+ fill(255);
+ text("Sketch (main PApplet) FPS: " + round(frameRate), 10, 20);
+ text("Average calc() FPS (if threads weren't capped): " + round(boidManager.getAverageCalcFPS()), 10, 40);
+ text("Average draw() FPS(if threads weren't capped): " + round(boidManager.getAverageDrawFPS()), 10, 60);
+}
+
+public void mouseReleased() {
+ if (mouseButton == LEFT) {
+ boidPath.addNode(new PVector(mouseX, mouseY));
+ }
+}
diff --git a/src/pthreading/PThread.java b/src/pthreading/PThread.java
index 517b33a..ffc9725 100644
--- a/src/pthreading/PThread.java
+++ b/src/pthreading/PThread.java
@@ -87,20 +87,20 @@ public void run() {
}
/**
- * The heart of a PThread. Override this method with code that should be
- * executed in a thread. Prefix calls to processing draw functions with
- * g. (eg. g.ellipse(50, 50, 50, 50).
- *
- * Internally, this method is called after {@link #calc()}.
- *
- * @see #calc()
+ * An optional override. Called at instantiation. Use this method set PGraphics'
+ * settings (e.g. g.colorMode(PApplet.HSB, 360, 100, 100); ), etc.
*/
- protected abstract void draw();
+ protected void setup() {
+ }
/**
* An optional override (you can do calculation-related code in
- * {@link #draw()}. This code will be executed in a thread. This is
- * useful when the 'unthread drawing' flag is true.
+ * {@link #draw()}, but putting it here may make more sense). This code will
+ * be executed in a thread.
+ *
+ * Putting calculation-related code here, rather than in draw(), is useful when
+ * the 'unthread drawing' flag is true, so that draw time and calc time can be
+ * compared.
*
* Internally, this method is called before {@link #draw()}.
*
@@ -109,12 +109,38 @@ public void run() {
protected void calc() {
}
+ /**
+ * The heart of a PThread. Override this method with code that should be
+ * executed in a thread. Prefix calls to processing draw functions with
+ * g. (eg. g.ellipse(50, 50, 50, 50).
+ *
+ * Internally, this method is called after {@link #calc()}.
+ *
+ * @see #calc()
+ */
+ protected abstract void draw();
+
void clearPGraphics() {
g.beginDraw();
g.clear();
g.endDraw();
}
+ void internalSetup() {
+ g.beginDraw();
+ setup();
+ g.endDraw();
+ }
+
+ /**
+ * Recreate PGraphics object when parent sketch is resized.
+ */
+ void resize() {
+ g.setSize(p.width, p.height);
+
+ internalSetup();
+ }
+
/**
* Returns time taken for the thread's draw() loop to execute. Can be used with
* {@link #getCalcFPS()} to determine if a thread is calculation bound or
@@ -124,7 +150,7 @@ void clearPGraphics() {
*/
final public float getDrawFPS() {
timing = true;
- return 1000/(drawTime / 1000000f);
+ return 1000 / (drawTime / 1000000f);
}
/**
@@ -136,7 +162,7 @@ final public float getDrawFPS() {
*/
final public float getCalcFPS() {
timing = true;
- return 1000/(calcTime / 1000000f);
+ return 1000 / (calcTime / 1000000f);
}
/**
diff --git a/src/pthreading/PThreadManager.java b/src/pthreading/PThreadManager.java
index e486a63..81a6679 100644
--- a/src/pthreading/PThreadManager.java
+++ b/src/pthreading/PThreadManager.java
@@ -7,6 +7,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -14,8 +16,6 @@
import javax.swing.Timer;
-import org.apache.commons.lang3.ClassUtils;
-
import processing.core.PApplet;
/**
@@ -40,10 +40,11 @@ public class PThreadManager {
private final PApplet p;
private final int targetFPS;
private final ScheduledExecutorService scheduler;
- private final HashMap> threads;
- private final HashMap threadFPS;
+ private final LinkedHashMap> threads;
+ private final LinkedHashMap threadFPS;
private boolean boundToParent = false;
private boolean unlinkComputeDraw = false;
+ private int sketchX, sketchY;
/**
* Constructs a new (empty) thread manager. The simplest constructor.
@@ -52,11 +53,13 @@ public class PThreadManager {
*/
public PThreadManager(PApplet p) {
this.p = p;
- threads = new HashMap>();
- threadFPS = new HashMap();
+ threads = new LinkedHashMap>();
+ threadFPS = new LinkedHashMap();
this.targetFPS = DEFAULT_FPS;
scheduler = Executors.newScheduledThreadPool(50);
p.registerMethod("dispose", this);
+ sketchX = p.width;
+ sketchY = p.height;
}
/**
@@ -70,11 +73,13 @@ public PThreadManager(PApplet p) {
*/
public PThreadManager(PApplet p, int targetFPS) {
this.p = p;
- threads = new HashMap>();
- threadFPS = new HashMap();
+ threads = new LinkedHashMap>();
+ threadFPS = new LinkedHashMap();
this.targetFPS = targetFPS;
scheduler = Executors.newScheduledThreadPool(50);
p.registerMethod("dispose", this);
+ sketchX = p.width;
+ sketchY = p.height;
}
/**
@@ -96,11 +101,13 @@ public PThreadManager(PApplet p, Class extends PThread> threadClass, int threa
}
this.p = p;
- threads = new HashMap>();
- threadFPS = new HashMap();
+ threads = new LinkedHashMap>();
+ threadFPS = new LinkedHashMap();
this.targetFPS = targetFPS;
scheduler = Executors.newScheduledThreadPool(threadCount);
p.registerMethod("dispose", this);
+ sketchX = p.width;
+ sketchY = p.height;
try {
Constructor extends PThread> constructor = threadClass.getDeclaredConstructor(PApplet.class); // todo
@@ -141,23 +148,40 @@ public PThreadManager(PApplet p, Class extends PThread> threadClass, int threa
}
this.p = p;
- threads = new HashMap>();
- threadFPS = new HashMap();
+ threads = new LinkedHashMap>();
+ threadFPS = new LinkedHashMap();
this.targetFPS = targetFPS;
scheduler = Executors.newScheduledThreadPool(threadCount);
p.registerMethod("dispose", this);
-
- final Class>[] constructorTypes = new Class[args.length + 1];
- constructorTypes[0] = PApplet.class;
- final Object[] completeArgs = new Object[args.length + 1];
- completeArgs[0] = p;
+ sketchX = p.width;
+ sketchY = p.height;
+
+ Class>[] constructorTypes;
+ Object[] completeArgs;
+ int inc = 1;
+
+ if (p.getClass() == threadClass.getDeclaredConstructors()[0].getParameterTypes()[0]
+ && p.getClass() != PApplet.class) { // PDE Workaround
+ inc = 2;
+ constructorTypes = new Class[args.length + inc];
+ constructorTypes[0] = p.getClass();
+ constructorTypes[1] = PApplet.class;
+ completeArgs = new Object[args.length + inc];
+ completeArgs[0] = p;
+ completeArgs[1] = p;
+ } else {
+ constructorTypes = new Class[args.length + inc];
+ constructorTypes[0] = PApplet.class;
+ completeArgs = new Object[args.length + inc];
+ completeArgs[0] = p;
+ }
for (int i = 0; i < args.length; i++) {
- completeArgs[i + 1] = args[i];
+ completeArgs[i + inc] = args[i];
if (ClassUtils.isPrimitiveWrapper(args[i].getClass())) {
- constructorTypes[i + 1] = ClassUtils.wrapperToPrimitive(args[i].getClass()); // TODO stable?
+ constructorTypes[i + inc] = ClassUtils.wrapperToPrimitive(args[i].getClass()); // TODO stable?
} else {
- constructorTypes[i + 1] = args[i].getClass();
+ constructorTypes[i + inc] = args[i].getClass();
}
}
@@ -255,17 +279,32 @@ public void addThread(Class extends PThread> threadClass, int threadCount, int
throw new IllegalArgumentException("threadCount should be more than 0");
}
- final Class>[] constructorTypes = new Class[args.length + 1];
- constructorTypes[0] = PApplet.class;
- final Object[] completeArgs = new Object[args.length + 1];
- completeArgs[0] = p;
+ Class>[] constructorTypes;
+ Object[] completeArgs;
+ int inc = 1;
+
+ if (p.getClass() == threadClass.getDeclaredConstructors()[0].getParameterTypes()[0]
+ && p.getClass() != PApplet.class) { // PDE Workaround
+ inc = 2;
+ constructorTypes = new Class[args.length + inc];
+ constructorTypes[0] = p.getClass();
+ constructorTypes[1] = PApplet.class;
+ completeArgs = new Object[args.length + inc];
+ completeArgs[0] = p;
+ completeArgs[1] = p;
+ } else {
+ constructorTypes = new Class[args.length + inc];
+ constructorTypes[0] = PApplet.class;
+ completeArgs = new Object[args.length + inc];
+ completeArgs[0] = p;
+ }
for (int i = 0; i < args.length; i++) {
- completeArgs[i + 1] = args[i];
+ completeArgs[i + inc] = args[i];
if (ClassUtils.isPrimitiveWrapper(args[i].getClass())) {
- constructorTypes[i + 1] = ClassUtils.wrapperToPrimitive(args[i].getClass()); // TODO stable?
+ constructorTypes[i + inc] = ClassUtils.wrapperToPrimitive(args[i].getClass()); // TODO stable?
} else {
- constructorTypes[i + 1] = args[i].getClass();
+ constructorTypes[i + inc] = args[i].getClass();
}
}
@@ -417,6 +456,15 @@ public void stopThreads() {
* {@link #bindDraw()}.
*/
public void draw() {
+
+ if (sketchX != p.width || sketchY != p.height) {
+ pauseThreads(); // TODO add delay?
+ threads.keySet().forEach(t -> t.resize());
+ resumeThreads();
+ }
+ sketchX = p.width;
+ sketchY = p.height;
+
if (unlinkComputeDraw) {
threads.forEach((thread, runnable) -> {
if (!runnable.isCancelled()) {
@@ -443,12 +491,19 @@ public void draw() {
public void unlinkComputeDraw() {
if (!unlinkComputeDraw) {
unlinkComputeDraw = true;
- threads.forEach((thread, value) -> {
- if (!value.isCancelled()) {
- threads.get(thread).cancel(true);
- addRunnable(thread);
+ Timer timer = new Timer(20, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ threads.forEach((thread, value) -> {
+ if (!value.isCancelled()) {
+ threads.get(thread).cancel(true);
+ addRunnable(thread);
+ }
+ });
}
});
+ timer.setRepeats(false);
+ timer.start();
}
}
@@ -496,7 +551,7 @@ public float getAverageDrawFPS() {
sum += thread.getDrawFPS();
}
}
- return count > 0 ? sum/count : 0;
+ return count > 0 ? sum / count : 0;
}
/**
@@ -515,7 +570,7 @@ public float getAverageCalcFPS() {
sum += thread.getCalcFPS();
}
}
- return count > 0 ? sum/count : 0;
+ return count > 0 ? sum / count : 0;
}
/**
@@ -578,15 +633,50 @@ public void dispose() {
*/
private void addRunnable(PThread thread) {
ScheduledFuture> scheduledRunnable;
+ thread.internalSetup();
if (unlinkComputeDraw) {
scheduledRunnable = scheduler.scheduleAtFixedRate(thread.noCalc, 0,
- 1000000 / (threadFPS.containsKey(thread) ? threadFPS.get(thread) : DEFAULT_FPS),
+ 1000000 / (threadFPS.containsKey(thread) ? threadFPS.get(thread) : targetFPS),
TimeUnit.MICROSECONDS);
} else {
scheduledRunnable = scheduler.scheduleAtFixedRate(thread.r, 0,
- 1000000 / (threadFPS.containsKey(thread) ? threadFPS.get(thread) : DEFAULT_FPS),
+ 1000000 / (threadFPS.containsKey(thread) ? threadFPS.get(thread) : targetFPS),
TimeUnit.MICROSECONDS);
}
threads.put(thread, scheduledRunnable);
}
-}
+
+ /**
+ * org.apache.commons.lang3.ClassUtils;
+ */
+ private static final class ClassUtils {
+ private static final Map, Class>> primitiveWrapperMap = new HashMap<>();
+ private static final Map, Class>> wrapperPrimitiveMap = new HashMap<>();
+ static {
+ primitiveWrapperMap.put(Boolean.TYPE, Boolean.class);
+ primitiveWrapperMap.put(Byte.TYPE, Byte.class);
+ primitiveWrapperMap.put(Character.TYPE, Character.class);
+ primitiveWrapperMap.put(Short.TYPE, Short.class);
+ primitiveWrapperMap.put(Integer.TYPE, Integer.class);
+ primitiveWrapperMap.put(Long.TYPE, Long.class);
+ primitiveWrapperMap.put(Double.TYPE, Double.class);
+ primitiveWrapperMap.put(Float.TYPE, Float.class);
+ primitiveWrapperMap.put(Void.TYPE, Void.TYPE);
+ for (final Map.Entry, Class>> entry : primitiveWrapperMap.entrySet()) {
+ final Class> primitiveClass = entry.getKey();
+ final Class> wrapperClass = entry.getValue();
+ if (!primitiveClass.equals(wrapperClass)) {
+ wrapperPrimitiveMap.put(wrapperClass, primitiveClass);
+ }
+ }
+ }
+
+ private static Class> wrapperToPrimitive(final Class> cls) {
+ return wrapperPrimitiveMap.get(cls);
+ }
+
+ private static boolean isPrimitiveWrapper(final Class> type) {
+ return wrapperPrimitiveMap.containsKey(type);
+ }
+ }
+}
\ No newline at end of file