-
Notifications
You must be signed in to change notification settings - Fork 0
Fix the rampant memory usage that was caused by compiling per… #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| package org.editor.nativemods; | ||
|
|
||
| import com.jhlabs.image.BrushedMetalFilter; | ||
| import java.awt.Color; | ||
| import java.awt.Graphics2D; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import org.editor.CanvasFrame; | ||
| import org.piccode.rt.Context; | ||
| import org.piccode.rt.PiccodeException; | ||
| import org.piccode.rt.PiccodeNumber; | ||
| import org.piccode.rt.PiccodeObject; | ||
| import org.piccode.rt.PiccodeReference; | ||
| import org.piccode.rt.PiccodeString; | ||
| import org.piccode.rt.PiccodeUnit; | ||
| import org.piccode.rt.PiccodeValue; | ||
| import org.piccode.rt.PiccodeValue.Type; | ||
| import org.piccode.rt.modules.NativeFunctionFactory; | ||
|
|
||
| /** | ||
| * | ||
| * @author hexaredecimal | ||
| */ | ||
| public class PiccodeBrushedMetalFilterModule { | ||
|
|
||
| public static void addFunctions() { | ||
| NativeFunctionFactory.create("brush_metal_new", List.of(), (args, namedArgs, frame) -> { | ||
| var bmFilter = new BrushedMetalFilter(); | ||
| return new PiccodeReference(bmFilter); | ||
| }, null); | ||
|
|
||
| NativeFunctionFactory.create("brush_metal_set_rad", List.of("filter", "rad"), (args, namedArgs, frame) -> { | ||
| var _filter = namedArgs.get("filter"); | ||
| var rad = namedArgs.get("rad"); | ||
|
|
||
| var ctx = frame == null | ||
| ? Context.top | ||
| : Context.getContextAt(frame); | ||
| var caller = ctx.getTopFrame().caller; | ||
|
|
||
| PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); | ||
| PiccodeValue.verifyType(caller, rad, Type.NUMBER); | ||
|
|
||
| var obj = (PiccodeReference) _filter; | ||
| var _filter_object = obj.deref(); | ||
| if (!(_filter_object instanceof BrushedMetalFilter)) { | ||
| throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); | ||
| } | ||
|
|
||
| var filter = (BrushedMetalFilter) _filter_object; | ||
| var radius = (int) (double) ((PiccodeNumber) rad).raw(); | ||
|
|
||
| filter.setRadius(radius); | ||
|
|
||
| return new PiccodeReference(_filter_object); | ||
| }, null); | ||
|
|
||
| NativeFunctionFactory.create("brush_metal_set_amount", List.of("filter", "amount"), (args, namedArgs, frame) -> { | ||
| var _filter = namedArgs.get("filter"); | ||
| var amount = namedArgs.get("amount"); | ||
|
|
||
| var ctx = frame == null | ||
| ? Context.top | ||
| : Context.getContextAt(frame); | ||
| var caller = ctx.getTopFrame().caller; | ||
|
|
||
| PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); | ||
| PiccodeValue.verifyType(caller, amount, Type.NUMBER); | ||
|
|
||
| var obj = (PiccodeReference) _filter; | ||
| var _filter_object = obj.deref(); | ||
| if (!(_filter_object instanceof BrushedMetalFilter)) { | ||
| throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); | ||
| } | ||
|
|
||
| var filter = (BrushedMetalFilter) _filter_object; | ||
| var _amount = (int) (double) ((PiccodeNumber) amount).raw(); | ||
|
|
||
| filter.setRadius(_amount); | ||
|
|
||
| return new PiccodeReference(_filter_object); | ||
| }, null); | ||
|
|
||
| NativeFunctionFactory.create("brush_metal_set_shine", List.of("filter", "shine"), (args, namedArgs, frame) -> { | ||
| var _filter = namedArgs.get("filter"); | ||
| var shine = namedArgs.get("shine"); | ||
|
|
||
| var ctx = frame == null | ||
| ? Context.top | ||
| : Context.getContextAt(frame); | ||
| var caller = ctx.getTopFrame().caller; | ||
|
|
||
| PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); | ||
| PiccodeValue.verifyType(caller, shine, Type.NUMBER); | ||
|
|
||
| var obj = (PiccodeReference) _filter; | ||
| var _filter_object = obj.deref(); | ||
| if (!(_filter_object instanceof BrushedMetalFilter)) { | ||
| throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); | ||
| } | ||
|
|
||
| var filter = (BrushedMetalFilter) _filter_object; | ||
| var _shine = (int) (double) ((PiccodeNumber) shine).raw(); | ||
|
|
||
| filter.setRadius(_shine); | ||
|
|
||
| return new PiccodeReference(_filter_object); | ||
| }, null); | ||
|
|
||
| } | ||
|
|
||
| } |
57 changes: 57 additions & 0 deletions
57
src/main/java/org/editor/nativemods/PiccodeFilterModule.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package org.editor.nativemods; | ||
|
|
||
| import java.awt.image.BufferedImage; | ||
| import java.awt.image.BufferedImageOp; | ||
| import java.util.List; | ||
| import org.piccode.rt.Context; | ||
| import org.piccode.rt.PiccodeException; | ||
| import org.piccode.rt.PiccodeReference; | ||
| import org.piccode.rt.PiccodeValue; | ||
| import org.piccode.rt.PiccodeValue.Type; | ||
| import org.piccode.rt.modules.NativeFunctionFactory; | ||
|
|
||
| /** | ||
| * | ||
| * @author hexaredecimal | ||
| */ | ||
| public class PiccodeFilterModule { | ||
|
|
||
| public static void addFunctions() { | ||
| NativeFunctionFactory.create("filter_apply", List.of("filter", "image"), (args, namedArgs, frame) -> { | ||
| var _filter = namedArgs.get("filter"); | ||
| var image = namedArgs.get("image"); | ||
|
|
||
| var ctx = frame == null | ||
| ? Context.top | ||
| : Context.getContextAt(frame); | ||
| var caller = ctx.getTopFrame().caller; | ||
|
|
||
| PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); | ||
| PiccodeValue.verifyType(caller, image, Type.REFERENCE); | ||
|
|
||
| var obj = (PiccodeReference) _filter; | ||
| var img = (PiccodeReference) image; | ||
| var _filter_object = obj.deref(); | ||
| var _buffered_image = img.deref(); | ||
|
|
||
| if (!(_filter_object instanceof BufferedImageOp)) { | ||
| throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); | ||
| } | ||
|
|
||
| if (!(_buffered_image instanceof BufferedImage)) { | ||
| throw new PiccodeException(caller.file, caller.line, caller.column, "Expected a buffer image. Found " + _buffered_image); | ||
| } | ||
|
|
||
|
|
||
| var filter = (BufferedImageOp) _filter_object; | ||
| var _image = (BufferedImage) _buffered_image; | ||
|
|
||
| var result = filter.filter(_image, null); | ||
|
|
||
| return new PiccodeReference(result); | ||
| }, null); | ||
|
|
||
|
|
||
| } | ||
|
|
||
| } |
92 changes: 92 additions & 0 deletions
92
src/main/java/org/editor/nativemods/PiccodeImageModule.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package org.editor.nativemods; | ||
|
|
||
| import java.awt.Color; | ||
| import java.awt.Graphics2D; | ||
| import java.awt.image.BufferedImage; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import org.editor.CanvasFrame; | ||
| import org.piccode.rt.Context; | ||
| import org.piccode.rt.PiccodeException; | ||
| import org.piccode.rt.PiccodeNumber; | ||
| import org.piccode.rt.PiccodeObject; | ||
| import org.piccode.rt.PiccodeReference; | ||
| import org.piccode.rt.PiccodeString; | ||
| import org.piccode.rt.PiccodeUnit; | ||
| import org.piccode.rt.PiccodeValue; | ||
| import org.piccode.rt.PiccodeValue.Type; | ||
| import org.piccode.rt.modules.NativeFunctionFactory; | ||
|
|
||
| /** | ||
| * | ||
| * @author hexaredecimal | ||
| */ | ||
| public class PiccodeImageModule { | ||
|
|
||
| public static void addFunctions() { | ||
|
|
||
| NativeFunctionFactory.create("image_new", List.of("w", "h"), (args, namedArgs, frame) -> { | ||
| var w = namedArgs.get("w"); | ||
| var h = namedArgs.get("h"); | ||
|
|
||
| var ctx = frame == null ? | ||
| Context.top | ||
| : Context.getContextAt(frame); | ||
| var caller = ctx.getTopFrame().caller; | ||
|
|
||
| PiccodeValue.verifyType(caller, w, Type.NUMBER); | ||
| PiccodeValue.verifyType(caller, h, Type.NUMBER); | ||
|
|
||
| var _w = (int) (double) ((PiccodeNumber) w).raw(); | ||
| var _h = (int) (double) ((PiccodeNumber) h).raw(); | ||
|
|
||
| var image = new BufferedImage(_w, _h, BufferedImage.TYPE_INT_ARGB); | ||
| return new PiccodeReference(image); | ||
| }, null); | ||
|
|
||
| NativeFunctionFactory.create("image_new_typed", List.of("w", "h", "type"), (args, namedArgs, frame) -> { | ||
| var w = namedArgs.get("w"); | ||
| var h = namedArgs.get("h"); | ||
| var type = namedArgs.get("type"); | ||
|
|
||
| var ctx = frame == null ? | ||
| Context.top | ||
| : Context.getContextAt(frame); | ||
| var caller = ctx.getTopFrame().caller; | ||
|
|
||
| PiccodeValue.verifyType(caller, w, Type.NUMBER); | ||
| PiccodeValue.verifyType(caller, h, Type.NUMBER); | ||
| PiccodeValue.verifyType(caller, type, Type.NUMBER); | ||
|
|
||
| var _w = (int) (double) ((PiccodeNumber) w).raw(); | ||
| var _h = (int) (double) ((PiccodeNumber) h).raw(); | ||
| var _type = (int) (double) ((PiccodeNumber) type).raw(); | ||
|
|
||
| var image = new BufferedImage(_w, _h, _type); | ||
| return new PiccodeReference(image); | ||
| }, null); | ||
|
|
||
| NativeFunctionFactory.create("image_get_context", List.of("img"), (args, namedArgs, frame) -> { | ||
| var img = namedArgs.get("img"); | ||
|
|
||
| var ctx = frame == null ? | ||
| Context.top | ||
| : Context.getContextAt(frame); | ||
| var caller = ctx.getTopFrame().caller; | ||
|
|
||
| PiccodeValue.verifyType(caller, img, Type.REFERENCE); | ||
|
|
||
| var _buffered_image = ((PiccodeReference)img).deref(); | ||
|
|
||
| if (!(_buffered_image instanceof BufferedImage)) { | ||
| throw new PiccodeException(caller.file, caller.line, caller.column, "Expected a buffer image. Found " + _buffered_image); | ||
| } | ||
|
|
||
| var bufferedmage = (BufferedImage) _buffered_image; | ||
| var gfx = bufferedmage.createGraphics(); | ||
| return new PiccodeReference(gfx); | ||
| }, null); | ||
|
|
||
| } | ||
|
|
||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Dispose previous gfx and reuse buffers; avoid leaks and allocations; repaint after compile
The current
compilealways allocates a newBufferedImage, reassignsgfx, and never disposes the previousGraphics2D. This leaks native resources and increases GC pressure. Also, if the component size hasn't changed, you can reuse the existing buffer.Proposed fix:
gfxbefore creating a new one (or reuse existing when size unchanged).renderif width/height are unchanged and just clear it.gfx.public void compile(Supplier<PiccodeValue> fx) { int width = getWidth(); int height = getHeight(); if (width <= 0 || height <= 0) { return; } - - render = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - - // Get the Graphics2D object - gfx = render.createGraphics(); - - // Enable transparency by drawing a fully transparent background - gfx.setComposite(AlphaComposite.Clear); - gfx.fillRect(0, 0, width, height); - - gfx.setColor(Color.BLACK); - // Switch back to normal composite mode for drawing - gfx.setComposite(AlphaComposite.SrcOver); - - fx.get(); + // Reuse buffer if size unchanged; otherwise dispose and recreate + boolean recreate = render == null || render.getWidth() != width || render.getHeight() != height; + if (recreate) { + if (gfx != null) { + try { gfx.dispose(); } catch (Exception ignore) {} + } + render = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + gfx = render.createGraphics(); + // Optional: quality hints for higher fidelity drawing to the offscreen buffer + gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gfx.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + gfx.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + } + + // Clear buffer to full transparency + gfx.setComposite(AlphaComposite.Clear); + gfx.fillRect(0, 0, width, height); + gfx.setComposite(AlphaComposite.SrcOver); + gfx.setColor(Color.BLACK); + + // Execute supplied drawing/compilation logic + fx.get(); + + // Ensure the latest image is presented + repaint(getVisibleRect()); }Note: If you plan to allow long-running drawing from background threads into
gfx, consider guardinggfxandrenderwith a lock and using a snapshot for painting to avoid tearing/races.📝 Committable suggestion
🤖 Prompt for AI Agents