|
LibraryBasics
The story of building a library, told with examples as parables
Library BasicsA very basic example to build a Processing library from scratch. package libraryexample;
import processing.core.*;
public class BasicLibrary {
PApplet parent;
public BasicLibrary(PApplet parent) {
this.parent = parent;
parent.registerDispose(this);
}
public void dispose() {
// anything in here will be called automatically when
// the parent applet shuts down. for instance, this might
// shut down a thread used by this library.
}
}Usually you'll need to pass "this" to a library's constructor so that the library can access functions from PApplet, e.g. graphics methods like line() and stroke() or loadImage(). Using a static initializer such as YourLibrary.init(this) is discouraged because it's not a good way to write OOP code, and makes assumptions about how your library connects to a sketch, and it encourages bad style that implies a single instance of your library. If you need to share information across library instances, use static variables or methods behind the scenes, but don't expose them to the user through the initializer. Using PConstantsIf you'd like to use constants such as RGB, use the following: public class BoringLibrary implements PConstants {which will make all the constants available to that class. Subclassing PGraphicsRelease 0148 included many changes to how libraries that implement PGraphics are handled. You can read about the changes in the developer's reference for PGraphics. Libraries that use the old PGraphics constructors will not work with release 0148 or later until these changes have been made to their API. The changes may be a little confusing at first, but a quick look at the source to other PGraphics implementations, such as PGraphicsOpenGL or PGraphicsPDF should clarify the way the new methods are used. Library Methodspublic void pre() Method that's called just after beginDraw(), meaning that it can affect drawing. public void draw() Method that's called at the end of draw(), but before endDraw(). public void post() Method called after draw has completed and the frame is done. No drawing allowed. public void mouseEvent(MouseEvent e) Called when a mouse event occurs in the parent applet. Drawing is allowed because mouse events are queued. public void keyEvent(KeyEvent e) Called when a key event occurs in the parent applet. Drawing is allowed because key events are queued. public void size(int width, int height) This will be called the first time an applet sets its size, but also any time that it's called while the PApplet is running. No drawing should occur inside of this method, because it may not be the case that the new renderer is yet valid and ready. use this only to flag the new size and prepare for the next frame. public void stop() Called to halt execution. Can be called by users, for instance movie.stop() will shut down a movie that's being played, or camera.stop() stops capturing video. server.stop() will shut down the server and shut it down completely. May be called multiple times. public void dispose() Called to free resources before shutting down. This should only be called by PApplet. The dispose() method is what gets called when the host applet is being shut down, so this should stop any threads, disconnect from the net, unload memory, etc. To register any of these methods with the parent, call parent.registerXxxxx(this), replacing Xxxxx with the name of the function (pre, draw, post, etc) that you'd like to use. Note that making these methods "public" is extremely important. When running inside Processing, anything left blank has public added by the preprocessor, meaning "void draw()" actually becomes "public void draw()". When drawing (or code that manipulates a sketch) can occurYou can only draw inside of pre(), draw(), mouseEvent(), or keyEvent() otherwise you will run into problems. If using OpenGL to render, for instance, you can easily make your machine (or at least the sketch) crash hard if you mess with the drawing surface when you're not supposed to. pre() and draw() happen while legitimate drawing is taking place, and the mouse/key events happen just before draw() events are called, they're queued up by the host applet until it's safe to draw. For this reason, you should use registerMouseEvent() and mouseEvent() (and same for the keys) to handle your events, rather than your class implementing MouseListener. For instance, to figure out what the mouse event is throwing back at you, this would be an example handler: public void mouseEvent(MouseEvent event) {
int x = event.getX();
int y = event.getY();
switch (event.getID()) {
case MouseEvent.MOUSE_PRESSED:
// do something for the mouse being pressed
break;
case MouseEvent.MOUSE_RELEASED:
// do something for mouse released
break;
case MouseEvent.MOUSE_CLICKED:
// do something for mouse clicked
break;
case MouseEvent.MOUSE_DRAGGED:
// do something for mouse dragged
break;
case MouseEvent.MOUSE_MOVED:
// umm... forgot
break;
}
}More on mouse handling can be found in Sun's documentation, which also covers things like modifiers (shift-click) and whatnot. Also check out the code for PApplet to see how ctrl-click is handled on Mac OS X so that it properly registers as a right-click. Structure of a Library FolderThe Sonia library by Amit Pitaru is a good example here. To make a library called sonia, you create a folder called "sonia" and within that, a subfolder named "library". The sonia folder should be placed inside the Processing "libraries" folder, or a user can place it inside their sketchbook folder. Inside "library", you'll find "sonia.jar". Anything that is found inside library will be exported with your sketch. For Sonia, the files are laid out as follows: /sonia.zip/sonia/library/export.txt /sonia.zip/sonia/library/sonia.jar /sonia.zip/sonia/library/JSynClasses.jar /sonia.zip/sonia/library/JSynV142.dll /sonia.zip/sonia/library/libJSynV142.jnilib If different sets of files should be exported with applets versus applications, you have two options. You can create a file called "export.txt" that specifies files to export for different scenarios, or you can create separate folders for each operating system your library supports and partition the files accordingly. Using export.txtSonia uses an export.txt file, which looks like this: # only export the jar file for applets.. # everything else is installed as a separate browser plugin applet=sonia.jar # application needs everything application=sonia.jar,JSynClasses.jar,JSynV142.dll,libJSynV142.jnilib This will include sonia.jar for applets, because in a web browser, the DLL files must be installed separately along with JSynClasses.jar. The # sign in front of a line means that the line is a comment, and it'll be ignored by the PDE. As of revision 0097, you can also specify what to export for other platforms as well (at least Mac OS X, Windows, Linux). For the example above, the application line could instead be changed to: application.macosx=sonia.jar,JSynClasses.jar,libJSynV142.jnilib application.windows=sonia.jar,JSynClasses.jar,JSynV142.dll Platform-specific exports will be checked first, and if they don't exist, the "application" line will be used. If neither exist (or export.txt doesn't exist), the entire contents of the library folder will be copied during export. You can optional further differentiate between 32 and 64 bit systems by appending 32 or 64 to the property. For example, if sonia had a special dll for working with 64 bit Windows, the export.txt file might look as follows: application.windows=sonia.jar,JSynClasses.jar,JSynV142.dll application.windows64=special64stuff.dll In this example, the PDE on 64 bit Windows would export special64stuff.dll in addition to exports listed in applications.winodws, and for 32 bit Windows, it would only export files from the application.windows list. Specifying exports using folders aloneThe above example can also be done without using an export.txt, but instead by reorganizing the files in the library as follows. /sonia.zip/sonia/library/sonia.jar /sonia.zip/sonia/library/macosx/JSynClasses.jar /sonia.zip/sonia/library/macosx/libJSynV142.jnilib /sonia.zip/sonia/library/windows/JSynClasses.jar /sonia.zip/sonia/library/windows/JSynV142.dll /sonia.zip/sonia/library/windows64/JSynClasses.jar /sonia.zip/sonia/library/windows64/JSynV142.dll /sonia.zip/sonia/library/windows64/special64stuff.dll As with the properties in the export.txt file, these can be optionally separated to distinguish exports between 32 and 64 bit versions of an operating system. However, only one folder will ever be used at a time for the classpath. This means that if you provide a specific library/windows64 folder, the library/windows folder will not be used when exporting applications for 64 bit Windows. Describing Your Library -- library.propertiesProcessing 2.0 will support installing libraries from within the PDE. To make the installation of your library as easy as possible, you should add file named library.properties to the base directory of your library. For example, if we were to extract sonia.zip, we should see the following file structure: sonia/library.properties sonia/library/sonia.jar ... In library.properties, you should include the following attributes: # UTF-8 supported. # The name of your library as you want it formatted name = Most Pixels Ever # List of authors. Links can be provided using the syntax [author name](url) authorList = [Daniel Shiffman](http://www.shiffman.net/) and Chris Kairalla # A web page for your library, NOT a direct link to where to download it url = http://www.mostpixelsever.com/ # The category of your library, must be one (or many) of the following: # "3D" "Animation" "Compilations" "Data" # "Fabrication" "Geometry" "GUI" "Hardware" # "I/O" "Language" "Math" "Simulation" # "Sound" "Utilities" "Typography" "Video & Vision" # # If a value other than those listed is used, your library will listed as "Other." category = Hardware # A short sentence (or fragment) to summarize the library's function. This will be # shown from inside the PDE when the library is being installed. Avoid repeating # the name of your library here. Also, avoid saying anything redundant like # mentioning that its a library. This should start with a capitalized letter, and # end with a period. sentence = Framework for spanning Processing sketches across multiple screens. # Additional information suitable for the Processing website. The value of # 'sentence' always will be prepended, so you should start by writing the # second sentence here. If your library only works on certain operating systems, # mention it here. paragraph = # Links in the 'sentence' and 'paragraph' attributes can be inserted using the # same syntax as for authors. That is, [here is a link to Processing](http://processing.org/) # A version number that increments once with each release. This # is used to compare different versions of the same library, and # check if an update is available. You should think of it as a # counter, counting the total number of releases you've had. version = 22 # This must be parsable as an int # The version as the user will see it. If blank, the version attribute will be used here prettyVersion = 0.98a # This is treated as a String Advertising Your LibraryStarting with Processing 2.0, libraries that are advertised on Processing's website also have the option of being advertised through the PDE. To support this, you'll need to include a library.properties file with your library and make a few changes to how it's hosted on your server. First, you must include the following attributes in library.properties: name, authorList, url, category, sentence, version. You'll also need to provide a static link to the latest version of your library, and host a copy of your library.properties alongside it. The result on your server should look something like this: http://www.example.com/awesomelib.zip/ (The latest version of awesomelib) http://www.example.com/awesomelib.txt/ (A copy of the library.properties file contained in awesomelib.zip) Once everything is in place, send your static links to the Processing Librarian: Elie Zananiri. We'll review your .properties file to make sure everything is in order and add your contribution to the list of advertised libraries. On our end, we'll only record the location of awesomelib.txt, and we'll assume that a copy of the library file is hosted at an address by the same name, except ending in zip. Each day, a script on processing.org will scrape the information from all the .txt files and aggregate it into a single file, which is then used by each user to browse what's available and check for updates. Even if your library is being advertised, you should still include the library.properties file inside your zip. Releasing an updateWhen you want to release a new version of your library, here's what you'll need to do:
That's it. The next time the aggregator script runs and your users start the PDE, they'll be notified that updates are available. Using Other Java Code As A LibrarySo long as the code is inside a package, it can be set up for use as a library. For instance, if you want to make a library called 'poo' set up a folder as follows: poo → library →poo.jar Then, the folder should be placed in the libraries folder inside the user's sketchbook folder to be recognized by Processing and its "Import Library" menu. You must first restart Processing in order to use the library. While this process may sound a little complicated, the intent is to make it easier for users than a typical Java IDE. A little added complexity for the developers of library code (who will generally be more advanced users) is traded for great simplicity by the users, since Processing is intended to target beginning programmers. Import Statements and How They WorkIf your library is sonia.jar, found at sonia/library/sonia.jar, all the packages found in sonia.jar will be added as imports into the user's sketch when they selected "Import Library". In the case of Sonia, an additional .jar file can be found in the sonia/library/ folder, jsyn.jar. The contents of jsyn.jar will not be added to the import statements. This is to avoid every library having a ridiculously large number of import statements. For instance, if you want to use the "video" library, you don't want all 15-20 packages for the QuickTime libraries listed there to confuse your users. Bottom line, if you want packages from the other .jar to be loaded by Processing, then you need to put those .class files into the main .jar file for the library (sonia/library/sonia.jar in this case). Creating .jar Files For Your LibrarySince your code is inside a package, you need to make sure that it's inside subfolders in the .jar file. It should be noted that jar files are simply .zip files (they can be opened with WinZip or Stuffit) with a "manifest" file. In the past, you may have used: javac *.java to compile your files. Once they're inside a packages, you must use: javac -d . *.java which will create folders and subfolders for the packages. For instance, for all the stuff in processing.core.* it would create: processing/ -> core/ ->PApplet.class PGraphics.class ..etc then you can jar that stuff up using: jar -cf core.jar processing or with a command line zip utility: zip -r core.jar processing The "Import Library" Menu ItemAll "Import Library" does is add the "import yourlibrary.*;" statement to the top of your sketch. If you've handwritten the import statements, then there's no need to use "Import Library". Adding Your Own Library EventsSo that your library can notify the host applet that something interesting has happened, this is how you implement an event method in the style of serialEvent, serverEvent, etc. public class FancyLibrary {
Method fancyEventMethod;
public YourLibrary(PApplet parent) {
// your library init code here...
// check to see if the host applet implements
// public void fancyEvent(FancyLibrary f)
try {
fancyEventMethod =
parent.getClass().getMethod("fancyEvent",
new Class[] { FancyLibrary.class });
} catch (Exception e) {
// no such method, or an error.. which is fine, just ignore
}
}
// then later, to fire that event
public void makeEvent() {
if (fancyEventMethod != null) {
try {
fancyEventMethod.invoke(parent, new Object[] { this });
} catch (Exception e) {
System.err.println("Disabling fancyEvent() for " + name +
" because of an error.");
e.printStackTrace();
fancyEventMethod = null;
}
}
}Using built-in functions from processing.coreMany methods in PApplet are made static for use by other libraries and code that interfaces with Processing. For instance, openStream() requires an applet object, but loadStrings() is a static method that can be run on any InputStream. See the developer's reference for more information about which methods are available. Accessing files from inside a libraryTo open files for use with a library, use the createInput() method. This is the most compatible means for loading data, and makes use of many hours of headaches that were the result of attempts to create functions that loaded data across platforms (Mac, Windows, and Linux) and circumstances (applet, application, and other). Similarly, to get an OutputStream for writing files, use createOutput(). The functions sketchPath(), sketchFile(), savePath(), dataPath(), and createPath() also facilitate reading and writing files relative to the sketch folder. They should be used to ensure that file I/O works consistently between your library and functions like loadImage() or loadStrings(). Their documentation can be seen as part of the developer's reference for PApplet. The variable sketchPath is also available for convenience, but in nearly all cases, the sketchPath() method is a better (and more compatible) route. The sketchFile() method works like sketchPath(), except that it wraps the path as a java.io.File object instead of the full path as a String. Getting an UnsupportedClassVersionError?Processing currently uses Java 1.5. If you compile your library code with a later Java release, it may be incompatible with the Java 1.5 class file format, which will give users a message like the following: java.lang.UnsupportedClassVersionError: blah/SomeClass (Unsupported major.minor version 49.0) This is because later versions of Java use their own class file format that's not backwards compatible. When compiling a library, use something like: javac -source 1.5 -target 1.5 -d . \
-classpath /path/to/core.jar path/to/java/source/*.javaLibrary Naming RulesLibraries, or classes inside them, should not be prefixed with "P" the way that the core Processing classes are (PImage, PGraphics, etc). It's tempting to prefix everything that way to identify it with Processing, but we'd like to reserve that naming for "official" things that are inside processing.core and other associated classes. Same goes for using "Processing", "Pro", or "P5" just like "P", or whether it's a prefix or a suffix. Similarly, please don't use 'processing' as the prefix for your library packages. We'd like to keep that name space clear for official things as well. |