diff --git a/editor/processing.plugin.core/META-INF/MANIFEST.MF b/editor/processing.plugin.core/META-INF/MANIFEST.MF
index c1f09b7de..f83e1d0a0 100644
--- a/editor/processing.plugin.core/META-INF/MANIFEST.MF
+++ b/editor/processing.plugin.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Processing Plugin Core
Bundle-SymbolicName: processing.plugin.core;singleton:=true
-Bundle-Version: 0.2.2.0
+Bundle-Version: 0.2.3.0
Bundle-Activator: processing.plugin.core.ProcessingCore
Bundle-Vendor: Processing.org
Require-Bundle: org.eclipse.core.runtime,
diff --git a/editor/processing.plugin.core/plugin.xml b/editor/processing.plugin.core/plugin.xml
index f3717a8b3..dd596f567 100644
--- a/editor/processing.plugin.core/plugin.xml
+++ b/editor/processing.plugin.core/plugin.xml
@@ -25,18 +25,33 @@
+
+ * Each instance of this class should only handle a single + * resource delta. Create a new one for each delta that needs + * to be processed. + *
+ * This class was inspired by the JyDT class of the same name + * implemented by Red Robin software. + *
+ * Right now only a full build is available, but the granularity + * of this class prevents unnecessary builds, which saves a lot + * of time, especially on workspaces with more than a handful + * of sketches. + *
+ * In the future this should be easy to integrate with a more
+ * incremental build process.
+ */
+public class IncrementalChangeProcessor {
+
+ private boolean fullBuildRequired;
+
+ private SketchProject sp;
+
+ /** Give it a project as context */
+ public IncrementalChangeProcessor(SketchProject sketchProject){
+ sp = sketchProject;
+ fullBuildRequired = false;
+ }
+
+ /** Indicate that a full build should be carried out */
+ private void setFullBuildRequired(){
+ fullBuildRequired = true;
+ }
+
+ /** Process an IResourceChangeEvent */
+ public boolean resourceChanged(IResourceChangeEvent event){
+ printEvent(event);
+ IResourceDelta delta = event.getDelta();
+ return this.resourceChanged(delta);
+ }
+
+ /** Process an IResourceDelta */
+ public boolean resourceChanged(IResourceDelta delta){
+ if (delta == null) return false;
+ printResourceChanges(delta);
+
+ IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() {
+ public boolean visit(IResourceDelta delta) throws CoreException {
+ switch (delta.getResource().getType()){
+ case IResource.ROOT:
+ // do nothing
+ break;
+ case IResource.PROJECT:
+ IProject changedProject = (IProject) delta.getResource();
+ if(changedProject != sp.getProject())
+ return false;
+ processProjectChange(delta);
+ break;
+ case IResource.FILE:
+ processFileChange(delta);
+ break;
+ case IResource.FOLDER:
+ processFolderChange(delta);
+ break;
+ default:
+ break;
+ }
+ return true; // visit children
+ }
+ };
+ try{
+ delta.accept(visitor);
+ } catch (CoreException ex){
+ ProcessingLog.logError(ex);
+ }
+ return fullBuildRequired;
+ }
+
+ /** Handle a change against the Project itself */
+ private void processProjectChange(IResourceDelta delta){
+ // Do nothing for now.
+ switch (delta.getFlags()) {
+ case IResourceDelta.DESCRIPTION:
+ break;
+ case IResourceDelta.OPEN:
+ setFullBuildRequired();
+ break;
+ case IResourceDelta.TYPE:
+ break;
+ case IResourceDelta.SYNC:
+ setFullBuildRequired();
+ break;
+ case IResourceDelta.MARKERS:
+ break;
+ case IResourceDelta.REPLACED:
+ setFullBuildRequired();
+ break;
+ case IResourceDelta.MOVED_TO:
+ setFullBuildRequired();
+ break;
+ case IResourceDelta.MOVED_FROM:
+ break;
+ default:
+ break;
+ }
+ }
+
+ /** Handle a resource change against a folder */
+ private void processFolderChange(IResourceDelta delta){
+ if (!sp.getProject().exists(delta.getProjectRelativePath()))
+ return;
+ switch(delta.getKind()){
+ case IResourceDelta.ADDED:
+ break;
+ case IResourceDelta.REMOVED:
+ if (delta.getFlags() == IResourceDelta.MOVED_TO)
+ processInterestingFolderMove(delta);
+ else
+ processInterestingFolderRemoval(delta);
+ break;
+ case IResourceDelta.CHANGED:
+ processInterestingFolderChange(delta);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /** Handle the change of a folder in the project */
+ private void processInterestingFolderChange(IResourceDelta delta){
+ // we don't really care that the folder changed at the moment
+
+ // switch(delta.getFlags()){
+ // case IResourceDelta.CONTENT:
+ // case IResourceDelta.DESCRIPTION:
+ // case IResourceDelta.OPEN:
+ // case IResourceDelta.TYPE:
+ // case IResourceDelta.SYNC:
+ // case IResourceDelta.MARKERS:
+ // case IResourceDelta.REPLACED:
+ // case IResourceDelta.MOVED_TO:
+ // case IResourceDelta.MOVED_FROM:
+ // default:
+ // break;
+ // }
+ }
+
+ /** Handle the removal of a folder in the project */
+ private void processInterestingFolderRemoval(IResourceDelta delta){
+ setFullBuildRequired();
+ }
+
+ /** Handle the move of a folder in the project */
+ private void processInterestingFolderMove(IResourceDelta delta){
+ setFullBuildRequired();
+ }
+
+ /** Handle the change of a file in the project */
+ private void processFileChange(IResourceDelta delta){
+ IFile resource = (IFile) delta.getResource();
+ // we only care about .pde files
+ if(!ProcessingCore.isProcessingFile(resource)) return;
+ if (!sp.getProject().exists(resource.getProjectRelativePath())) return;
+
+ // System.out.println(delta.getProjectRelativePath() + " exists in " + sp.getProject().getName());
+ setFullBuildRequired();
+
+ //switch (delta.getKind()){
+ //case IResourceDelta.ADDED:
+ //case IResourceDelta.REMOVED:
+ //case IResourceDelta.CHANGED:
+ // int deltaFlags = delta.getFlags();
+ // switch(deltaFlags){
+ // case IResourceDelta.CONTENT:
+ // case IResourceDelta.ENCODING:
+ // case IResourceDelta.TYPE:
+ // case IResourceDelta.SYNC:
+ // case IResourceDelta.MARKERS:
+ // case IResourceDelta.REPLACED:
+ // case IResourceDelta.MOVED_TO:
+ // case IResourceDelta.MOVED_FROM:
+ // default:
+ // break;
+ // }
+ // break;
+ // default:
+ // break;
+ // }
+ }
+
+ /** Debug code */
+ private static void printChange(IResourceDelta delta, StringBuffer buffer) {
+ buffer.append("CHANGED ");
+ buffer.append(delta.getFlags());
+ switch (delta.getFlags()) {
+ case IResourceDelta.CONTENT:
+ buffer.append(" (CONTENT)");
+ break;
+ case IResourceDelta.ENCODING:
+ buffer.append(" (ENCODING)");
+ break;
+ case IResourceDelta.DESCRIPTION:
+ buffer.append(" (DESCRIPTION)");
+ break;
+ case IResourceDelta.OPEN:
+ buffer.append(" (OPEN)");
+ break;
+ case IResourceDelta.TYPE:
+ buffer.append(" (TYPE)");
+ break;
+ case IResourceDelta.SYNC:
+ buffer.append(" (SYNC)");
+ break;
+ case IResourceDelta.MARKERS:
+ buffer.append(" (MARKERS)");
+ break;
+ case IResourceDelta.REPLACED:
+ buffer.append(" (REPLACED)");
+ break;
+ case IResourceDelta.MOVED_TO:
+ buffer.append(" (MOVED_TO)");
+ break;
+ case IResourceDelta.MOVED_FROM:
+ buffer.append(" (MOVED_FROM)");
+ break;
+ default:
+ buffer.append(" (
* The builder is compatible with the PDE, and it expects sketches to be laid out with the * same folder structure. It may store metadata and temporary build files in the sketch * file system but these will not change how the PDE interacts with it. Users should be * able to use the PDE interchangeably with this builder. - *
** Though this implements the Incremental Project Builder, the preprocessor is not incremental * and forces all builds to be full builds. To save a little bit of time, any build request is * treated as a full build without an inspection of the resource delta. - *
*/ public class SketchBuilder extends IncrementalProjectBuilder{ /** The identifier for the Processing builder (value"processing.plugin.core.processingbuilder"). */
public static final String BUILDER_ID = ProcessingCore.PLUGIN_ID + ".sketchBuilder";
+ /** Parent marker for Processing created markers (value "processing.plugin.core.processingMarker"). */
+ public static final String PROCESSINGMARKER = ProcessingCore.PLUGIN_ID + ".processingMarker";
+
/** Problem marker for processing preprocessor issues (value "processing.plugin.core.preprocError"). */
public static final String PREPROCMARKER = ProcessingCore.PLUGIN_ID + ".preprocError";
@@ -111,9 +111,14 @@ public class SketchBuilder extends IncrementalProjectBuilder{
/** Full paths to jars required to compile the sketch */
private ArrayList
+ * Reset any state from previous builds.
+ */
+ protected void clean(IProgressMonitor monitor) throws CoreException{
+ SketchProject sketch = SketchProject.forProject(this.getProject());
+ deleteP5ProblemMarkers(sketch.getProject());
srcFolderPathList = new ArrayList
* For now all builds are full builds because the preprocessor does not - * handle incremental builds. - *
- * + * handle incremental builds. */ protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException{ IProject project = this.getProject(); SketchProject sketch = SketchProject.forProject(project); - - switch (kind){ - case FULL_BUILD: - return this.fullBuild(sketch, monitor); - case AUTO_BUILD: - return (fullBuildRequired(project)) ? this.fullBuild(sketch, monitor) : null; -// return this.autoBuild(sketch, monitor); - case INCREMENTAL_BUILD: - return (fullBuildRequired(project)) ? this.fullBuild(sketch, monitor) : null; -// return this.incrementalBuild(sketch, monitor); - default: - return null; + switch (kind) { + case FULL_BUILD: + return this.fullBuild(sketch, monitor); + case AUTO_BUILD: + return this.autoBuild(sketch, monitor); + case INCREMENTAL_BUILD: + return this.incrementalBuild(sketch, monitor); + default: + return null; // everything falls through to return null } } - - /** - * Returns false if the resource change definitely didn't affect this project. - *- * Runs simple checks to try and rule out a build. If it can't find a reason - * to rule out a build, it defaults to true. Hopefully this will spare a few - * CPU cycles, though there is a lot of room for improvement here. - */ - private boolean fullBuildRequired(IProject proj) { - if (proj == null) return false; - IResourceDelta delta = this.getDelta(proj); - if (delta == null) return false; - if (delta.getResource().getType() == IResource.PROJECT){ - IProject changedProject = (IProject) delta.getResource(); - if (changedProject != proj) return false; - } - return true; + + /** Handles platform auto builds */ + protected IProject[] autoBuild(SketchProject sketchProject, IProgressMonitor monitor) throws CoreException{ + //System.err.println("Auto Build"); + IResourceDelta delta = this.getDelta(this.getProject()); + IncrementalChangeProcessor changeProcessor = new IncrementalChangeProcessor(sketchProject); + boolean shouldBuild = changeProcessor.resourceChanged(delta); + if (shouldBuild) + fullBuild(sketchProject, monitor); + return null; + } + + /** Incremental builds are ignored. */ + protected IProject[] incrementalBuild(SketchProject sketchProject, IProgressMonitor monitor){ + //System.err.println("Incremental Build"); + + // triggered by launching a sketch or explicitly by a user iff auto build is off + // if auto build is on, launching a sketch triggers an auto build first + // save a few cycles by ignoring these + return null; } /** @@ -188,7 +189,9 @@ public class SketchBuilder extends IncrementalProjectBuilder{ * This can be a long running process, so we use a monitor. */ protected IProject[] fullBuild( SketchProject sketchProject, IProgressMonitor monitor) throws CoreException { - clean(sketchProject, monitor); // tabula rasa +// System.err.println("Full Build of " + sketchProject.getProject().getName()); + + clean(monitor); // tabula rasa IProject sketch = sketchProject.getProject(); if ( sketch == null || !sketch.isAccessible() ){ @@ -481,16 +484,8 @@ public class SketchBuilder extends IncrementalProjectBuilder{ /** Delete all of the existing P5 problem markers. */ protected static void deleteP5ProblemMarkers(IResource resource) throws CoreException{ - if(resource != null && resource.exists()){ - resource.deleteMarkers(SketchBuilder.PREPROCMARKER, true, IResource.DEPTH_INFINITE); - resource.deleteMarkers(SketchBuilder.COMPILEMARKER, true, IResource.DEPTH_INFINITE); - } - } - - /** Purge all the Processing set problem markers from the Processing sketch. */ - protected static void deleteP5ProblemMarkers(SketchProject sketch)throws CoreException{ - if(sketch != null) - deleteP5ProblemMarkers(sketch.getProject()); + if(resource != null && resource.exists()) + resource.deleteMarkers(SketchBuilder.PROCESSINGMARKER , true, IResource.DEPTH_INFINITE); } /** @@ -500,7 +495,6 @@ public class SketchBuilder extends IncrementalProjectBuilder{ * to a specific file it will be marked against the project and the line will not be marked. */ private void reportProblem(String message, IResource problemFile, int lineNumber, boolean isError){ - // translate error messages to a friendlier form if (message.equals("expecting RCURLY, found 'null'")) message = "Found one too many { characters without a } to match it."; @@ -528,17 +522,18 @@ public class SketchBuilder extends IncrementalProjectBuilder{ /** * Check for interruptions. *
- * The build process is rather long so if the user cancels things toss an exception. + * The build process can run long, so if the user cancels things toss an exception. * Builds also tie up the workspace, so check to see if something is demanding to * interrupt it and let it. Usually these interruptions would force a rebuild of the - * system anyhow, so save some CPU cycles and time. + * system anyhow, nullifying the work being done now, so save some cycles and let + * it budge in line. *
* @return true if the build is hogging the resource thread * @throws OperationCanceledException is the user cancels */ private boolean checkCancel(IProgressMonitor monitor){ - if (monitor.isCanceled()){ throw new OperationCanceledException(); } - if (isInterrupted()){ return true; } + if (monitor.isCanceled()) throw new OperationCanceledException(); + if (isInterrupted()) return true; return false; } }