I am developing a small application for creating animations just for myself. I want to create the following structure: the program has folders, and those folders have layers. The program has a main folder, to which we add all other layers and folders. That is, there are two main classes: Folder and Layer.
Layer is nothing special, it just loads images, adds effects, etc.
Folder can also add effects, etc., but in addition, Folder has the following structure:
class Folder {
ArrayList<Folder> folders;
ArrayList<Layer> layers;
PGraphics pg;
public Folder() {
}
void render(PGraphics canvas) {
pg=createGraphics(canvas.width, canvas.height);
pg.beginDraw();
for (int i=0; i<folders.size(); i++) {
folder.render(pg);
}
for (int i=0; i<layers.size(); i++) {
layer.render(pg);
}
pg.endDraw();
/*
adding effects etc.
*/
canvas.image(pg);
}
}
As you can see, the problem lies in the render() method. If there are too many nested folders, there is a chance of getting an OhtOfMemoryError, which is unacceptable. How can this be avoided? It is impossible to render all folders to a single PG, as different effects are applied to them. At first, I thought about rendering the folders in stages, starting with the most recent ones, and saving the images somewhere in the application directory, but hereās the thing: if I try to load an image before it is completely saved correctly, Processing wonāt give an error, it will just load the image cropped.
Iām writing code in APDE, by the way. I would be very grateful for any help!
I assume the render method is being called from the draw() method which means every folder creates a new PGraphics (pg) object every frame. I suggest that you create pg in the Folder constructor and only chnage it if the parent canvas has changed size like this.
class Folder {
ArrayList<Folder> folders;
ArrayList<Layer> layers;
PGraphics pg;
// default ctor just in case your code requires one as
public Folder() {
pg = createGraphics(width, height);
}
// This is the ctor you are most like;y to need
public Folder(PGraphics canvas) {
pg = createGraphics(canvas.width, canvas.height);
}
void render(PGraphics canvas) {
if(pg.width != canvas.width || pg.height != canvas.height){
pg = createGraphics(canvas.width, canvas.height);
}
pg.beginDraw();
pg.clear();
for (int i=0; i<folders.size(); i++) {
folder.render(pg);
}
for (int i=0; i<layers.size(); i++) {
layer.render(pg);
}
pg.endDraw();
/*
adding effects etc.
*/
canvas.image(pg);
}
}
Yes, I understand what you mean. The canvas size will never change during rendering, so it is possible to create pg in advance, but I donāt think that will completely solve the problem. Unfortunately, I donāt have a large heap (192 MB), and I get an OutOfMemoryError if I create 10-20 PGS at 1080p or 3-5 PGS at 4k. This seems fine for 1080p, but itās a problem when rendering 4k.
When you call a function to be executed the computer will create a stack frame that will be pushed on to the processing stack.
The stack frame will store the following information
the function parameters
all local variables
the return address (where to find the next instruction once the function is finished)
Now in your program you are using recursion so lets assume the the top-level folder (F0) has the children, grand-children and great-grand-children as shown below
As the program executes we can trace the order of execution and see the state of the stack as each function is executed.
In each line the stack frame on the right identifies the function currently being executed. Noticed that the maximum stack frame depth is 4 and that every function is executed once.
STACK
Bottom <-------> Top
F0 F1 F11
F0 F1 F12 F121
F0 F1 F12 F122
F0 F1 F12
F0 F1 F13
F0 F1
F0 F2 F21
F0 F2 F22
F0 F2
F0
In your original code the pg was a local variable so that every frame contained its own PGraphics object and it would exist as long as the stack frame existed. So as the recursive depth increases the amount of memory increases rapidly which is why I made my previous suggestion.
Your recursive function will always terminate because eventually it will come across Folder objects with no children, in my example these are
F11 F121 F122 F13 F21 F22
So some things to strive for
avoid creating memory hungry local variables
avoid recreating images that have not changed
lazy update when creating images
Without more detailed information I cannot really provide a more definitive answer but I hope this explanation will give you an insight into where you memory is being used.
I understand. In my project, āfoldersā are not file system objects; this is simply class name. They can contain layers or other folders inside them. Layers, in turn, contain frame images. Layers and folders can have various filters, effects, overlays, etc.
Exemple:
Actually, I donāt have any actual code yet. Iām just experimenting and thinking about how to write the code correctly so that there wonāt be any difficulties later on. So i have nothing to share here.
Itās a little difficult for me to understand. Sorry, English is not my native language.
But it seems that you mean to make PG a global variable and render all folders to it. There is one problem related to the fact that each folder/layer has its own set of effects and filters. Therefore, I cannot render everything to one PG. But you can set a limit, for example, a maximum of two nested folders with effects. Then I can make two nested folders with effects/filters + an infinite number without.
Something like this:
class Folder {
ArrayList<Folder> folders;
ArrayList<Layer> layers;
Effect effect;
Filter filter;
String name;
PGraphics pg;
int folderDepth;
public Folder(String name, PGraphics canvas) {
this.name=name;
folderDepth=0;
}
public void addFilter(String filterType){
if(folderDepth<2){
//adding filter
}
}
public void createFolder(String path){
/*
create a folder at this path if it does not exist
and set folderDepth+1
*/
}
void render(PGraphics canvas) {
if(filter==null && effect==null){
for (int i=0; i<folders.size(); i++) {
folders.render(canvas);
}
for (int i=0; i<layers.size(); i++) {
layers.render(canvas);
}
}
else{
PGraphics pg;
pg = createGraphics(canvas.width, canvas.height);
pg.beginDraw();
pg.clear();
for (int i=0; i<folders.size(); i++) {
folders.render(pg);
}
for (int i=0; i<layers.size(); i++) {
layers.render(pg);
}
pg.endDraw();
}
/*
applying effects and filters
*/
canvas.image(pg);
}
}
Yeah, I think thatās the best solution for now.
Thanks.
Ignore my last post it was simply to show how recursion works and things to avoid that might cause memory issues.
In your original post you created a new PGraphics object every time render was executed replacing the old PGraphics object. The problem is that the memory occupied by the old PGraphics object is is not immediately released back to the system, instead it has to wait for Javaās garbage collection. So when using recursion many āoldā objects can remain in memory for some time causing out-of-memory errors. My reply was to create the graphics object once in the constructor and reuse it every time render is execute which is much more efficient.
If your sketch is not resizable then this code can be removed from the render function.
I wasnāt suggesting that sorry if I didnāt explain myself well.
Looking at your original code it seems that a Folderās graphic is some composite of all the graphics created in its sub-folders and sub-layers. If that is true then applying some recursive depth limit is totally undesirable and defeats the purpose of the application.
Provided the local variables in your render function do not require large amounts of RAM then recursion should not cause a memory problem.