mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-11 01:32:08 +10:00
give the cache a chance to prepare all the components in multiple
threads
This commit is contained in:
@@ -31,6 +31,7 @@ import org.diylc.common.ComponentType;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.utils.Pair;
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,39 @@ public class DrawingCache {
|
||||
|
||||
public static DrawingCache Instance = new DrawingCache();
|
||||
|
||||
/**
|
||||
* Prepares cached renderings of those components from the specified map that have {@link ComponentDescriptor#enableCache()} set to true.
|
||||
*
|
||||
* @param components
|
||||
* @param g2d
|
||||
* @param outlineMode
|
||||
* @param project
|
||||
* @param zoom
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bulkPrepare(Map<IDIYComponent<?>, ComponentState> components, G2DWrapper g2d, boolean outlineMode, Project project, double zoom) {
|
||||
// run all renders in parallel to utilize all CPU cores
|
||||
components.entrySet().stream().parallel().forEach(x -> {
|
||||
ComponentType type = ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) x.getKey().getClass());
|
||||
|
||||
if (type.getEnableCache()) {
|
||||
renderAndCache(x.getKey(), g2d, x.getValue(), outlineMode, project, zoom);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a component onto the specified {@link G2DWrapper}. If the component has {@link ComponentDescriptor#enableCache()} set to true,
|
||||
* the drawing will come from the cache. Otherwise, it will be drawn as usual.
|
||||
*
|
||||
* @param component
|
||||
* @param g2d
|
||||
* @param componentState
|
||||
* @param outlineMode
|
||||
* @param project
|
||||
* @param zoom
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void draw(IDIYComponent<?> component, G2DWrapper g2d, ComponentState componentState,
|
||||
boolean outlineMode, Project project, double zoom) {
|
||||
@@ -61,6 +95,67 @@ public class DrawingCache {
|
||||
// if we need to apply caching
|
||||
Point firstPoint = component.getControlPoint(0);
|
||||
|
||||
CacheValue value = renderAndCache(component, g2d, componentState, outlineMode, project, zoom);
|
||||
|
||||
long componentStart = System.nanoTime();
|
||||
|
||||
// temporarily scale the graphic to compensate for the zoom, since we are rendering an image with the zoom appled
|
||||
if (Math.abs(1.0 - zoom) > 1e-4) {
|
||||
g2d.getCanvasGraphics().scale(1 / zoom, 1 / zoom);
|
||||
}
|
||||
try {
|
||||
// draw cached image
|
||||
g2d.getCanvasGraphics().drawImage(value.getImage(), (int) ((firstPoint.x - value.getDx()) * zoom),
|
||||
(int) ((firstPoint.y - value.getDy())* zoom), null);
|
||||
} finally {
|
||||
if (Math.abs(1.0 - zoom) > 1e-4) {
|
||||
g2d.getCanvasGraphics().scale(zoom, zoom);
|
||||
}
|
||||
}
|
||||
|
||||
long componentEnd = System.nanoTime();
|
||||
|
||||
Pair<Counter, Counter> stats;
|
||||
String key = component.getClass().getCanonicalName().replace("org.diylc.components.", "");
|
||||
if (renderStatsByType.containsKey(key)) {
|
||||
stats = renderStatsByType.get(key);
|
||||
} else {
|
||||
stats = new Pair<Counter, Counter>(new Counter(), new Counter());
|
||||
renderStatsByType.put(key, stats);
|
||||
}
|
||||
stats.getSecond().add(componentEnd - componentStart);
|
||||
|
||||
// copy over tracking area from the cache
|
||||
g2d.merge(value.getCurrentArea(), value.getContinuityPositiveAreas(), value.getContinuityNegativeAreas());
|
||||
} else {
|
||||
// no caching, just draw as usual
|
||||
component.draw(g2d, componentState, outlineMode, project, g2d);
|
||||
}
|
||||
g2d.stopTracking();
|
||||
g2d.stopTrackingContinuityArea();
|
||||
}
|
||||
|
||||
public void logStats() {
|
||||
int totalSizeMB = imageCache.values().stream().map(x -> x.image.getData().getDataBuffer().getSize()).reduce(0, Integer::sum) * 4 / 1024 / 1024; // 4 bytes per pixel
|
||||
LOG.debug(String.format("Render cache contains %d elements, approx size is %d MB", imageCache.size(), totalSizeMB));
|
||||
|
||||
String mapAsString = renderStatsByType.entrySet().stream().sorted((e1, e2) -> -Long.compare(e1.getValue().getFirst().getNanoTime() + e1.getValue().getSecond().getNanoTime(),
|
||||
e2.getValue().getFirst().getNanoTime() + e2.getValue().getSecond().getNanoTime()))
|
||||
.map(e -> e.getKey() + ": render: " + e.getValue().getFirst() + ", paste: " + e.getValue().getSecond())
|
||||
.collect(Collectors.joining("; ", "{", "}"));
|
||||
|
||||
LOG.debug("Cache render distribution stats: " + mapAsString);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
imageCache.clear();
|
||||
}
|
||||
|
||||
private CacheValue renderAndCache(IDIYComponent<?> component, G2DWrapper g2d, ComponentState componentState,
|
||||
boolean outlineMode, Project project, double zoom) {
|
||||
// if we need to apply caching
|
||||
Point firstPoint = component.getControlPoint(0);
|
||||
|
||||
// cache hit!
|
||||
CacheValue value = null;
|
||||
if (imageCache.containsKey(component))
|
||||
@@ -142,58 +237,7 @@ public class DrawingCache {
|
||||
imageCache.put(component, value);
|
||||
}
|
||||
|
||||
long componentStart = System.nanoTime();
|
||||
|
||||
// temporarily scale the graphic to compensate for the zoom, since we are rendering an image with the zoom appled
|
||||
if (Math.abs(1.0 - zoom) > 1e-4) {
|
||||
g2d.getCanvasGraphics().scale(1 / zoom, 1 / zoom);
|
||||
}
|
||||
try {
|
||||
// draw cached image
|
||||
g2d.getCanvasGraphics().drawImage(value.getImage(), (int) ((firstPoint.x - value.getDx()) * zoom),
|
||||
(int) ((firstPoint.y - value.getDy())* zoom), null);
|
||||
} finally {
|
||||
if (Math.abs(1.0 - zoom) > 1e-4) {
|
||||
g2d.getCanvasGraphics().scale(zoom, zoom);
|
||||
}
|
||||
}
|
||||
|
||||
long componentEnd = System.nanoTime();
|
||||
|
||||
Pair<Counter, Counter> stats;
|
||||
String key = component.getClass().getCanonicalName().replace("org.diylc.components.", "");
|
||||
if (renderStatsByType.containsKey(key)) {
|
||||
stats = renderStatsByType.get(key);
|
||||
} else {
|
||||
stats = new Pair<Counter, Counter>(new Counter(), new Counter());
|
||||
renderStatsByType.put(key, stats);
|
||||
}
|
||||
stats.getSecond().add(componentEnd - componentStart);
|
||||
|
||||
// copy over tracking area from the cache
|
||||
g2d.merge(value.getCurrentArea(), value.getContinuityPositiveAreas(), value.getContinuityNegativeAreas());
|
||||
} else {
|
||||
// no caching, just draw as usual
|
||||
component.draw(g2d, componentState, outlineMode, project, g2d);
|
||||
}
|
||||
g2d.stopTracking();
|
||||
g2d.stopTrackingContinuityArea();
|
||||
}
|
||||
|
||||
public void logStats() {
|
||||
int totalSizeMB = imageCache.values().stream().map(x -> x.image.getData().getDataBuffer().getSize()).reduce(0, Integer::sum) * 4 / 1024 / 1024; // 4 bytes per pixel
|
||||
LOG.debug(String.format("Render cache contains %d elements, approx size is %d MB", imageCache.size(), totalSizeMB));
|
||||
|
||||
String mapAsString = renderStatsByType.entrySet().stream().sorted((e1, e2) -> -Long.compare(e1.getValue().getFirst().getNanoTime() + e1.getValue().getSecond().getNanoTime(),
|
||||
e2.getValue().getFirst().getNanoTime() + e2.getValue().getSecond().getNanoTime()))
|
||||
.map(e -> e.getKey() + ": render: " + e.getValue().getFirst() + ", paste: " + e.getValue().getSecond())
|
||||
.collect(Collectors.joining("; ", "{", "}"));
|
||||
|
||||
LOG.debug("Cache render distribution stats: " + mapAsString);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
imageCache.clear();
|
||||
return value;
|
||||
}
|
||||
|
||||
private class CacheValue {
|
||||
|
||||
@@ -255,12 +255,9 @@ public class DrawingManager {
|
||||
|
||||
// g2dWrapper.resetTx();
|
||||
|
||||
// componentAreaMap.clear();
|
||||
// put components and their states in a map
|
||||
Map<IDIYComponent<?>, ComponentState> componentStateMap = new HashMap<IDIYComponent<?>, ComponentState>();
|
||||
for (IDIYComponent<?> component : project.getComponents()) {
|
||||
// Do not draw the component if it's filtered out.
|
||||
if (filter != null && !filter.testComponent(component)) {
|
||||
continue;
|
||||
}
|
||||
ComponentState state = ComponentState.NORMAL;
|
||||
if (drawOptions.contains(DrawOption.SELECTION) && selectedComponents.contains(component)) {
|
||||
if (dragInProgress) {
|
||||
@@ -269,6 +266,23 @@ public class DrawingManager {
|
||||
state = ComponentState.SELECTED;
|
||||
}
|
||||
}
|
||||
componentStateMap.put(component, state);
|
||||
}
|
||||
|
||||
boolean outlineMode = drawOptions.contains(DrawOption.OUTLINE_MODE);
|
||||
|
||||
// give the cache a chance to prepare all the components in multiple threads
|
||||
if (drawOptions.contains(DrawOption.ENABLE_CACHING)) {
|
||||
DrawingCache.Instance.bulkPrepare(componentStateMap, g2dWrapper, outlineMode, project, zoom);
|
||||
}
|
||||
|
||||
// componentAreaMap.clear();
|
||||
for (IDIYComponent<?> component : project.getComponents()) {
|
||||
// Do not draw the component if it's filtered out.
|
||||
if (filter != null && !filter.testComponent(component)) {
|
||||
continue;
|
||||
}
|
||||
ComponentState state = componentStateMap.get(component);
|
||||
// Do not track the area if component is not invalidated and was
|
||||
// drawn in the same state.
|
||||
boolean trackArea = lastDrawnStateMap.get(component) != state;
|
||||
@@ -289,7 +303,7 @@ public class DrawingManager {
|
||||
if (drawOptions.contains(DrawOption.ENABLE_CACHING)) // go through the DrawingCache
|
||||
DrawingCache.Instance.draw(component, g2dWrapper, state, drawOptions.contains(DrawOption.OUTLINE_MODE), project, zoom);
|
||||
else // go stragiht to the wrapper
|
||||
component.draw(g2dWrapper, state, drawOptions.contains(DrawOption.OUTLINE_MODE), project, g2dWrapper);
|
||||
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
||||
|
||||
if (g2dWrapper.isTrackingContinuityArea()) {
|
||||
LOG.info("Component " + component.getName() + " of type " + component.getClass().getName() + " did not stop tracking continuity area.");
|
||||
@@ -652,32 +666,53 @@ public class DrawingManager {
|
||||
List<Area> newAreas = new ArrayList<Area>();
|
||||
boolean[] consumed = new boolean[areas.size()];
|
||||
for (int i = 0; i < areas.size(); i++) {
|
||||
for (int j = i + 1; j < areas.size(); j++) {
|
||||
if (consumed[j])
|
||||
continue;
|
||||
Area a1 = areas.get(i);
|
||||
Rectangle2D bounds1 = a1.getBounds2D();
|
||||
|
||||
Map<Area, Integer> candidates = new HashMap<Area, Integer>();
|
||||
|
||||
for (int j = i + 1; j < areas.size(); j++) {
|
||||
// if (consumed[j])
|
||||
// continue;
|
||||
Area a2 = areas.get(j);
|
||||
Area intersection = null;
|
||||
if (a1.getBounds2D().intersects(a2.getBounds())) {
|
||||
intersection = new Area(a1);
|
||||
intersection.intersect(a2);
|
||||
}
|
||||
// if the two areas intersect, make a union and consume the second area
|
||||
if (intersection != null && !intersection.isEmpty()) {
|
||||
a1.add(a2);
|
||||
consumed[j] = true;
|
||||
} else { // maybe there's a connection between them
|
||||
Rectangle2D bounds2 = a2.getBounds2D();
|
||||
|
||||
// maybe there's a direct connection between them, try that first because it's faster than area overlap
|
||||
for (Connection p : connections) { // use getBounds to optimize the computation, don't get into complex math if not needed
|
||||
if ((a1.getBounds().contains(p.getP1()) && a2.getBounds().contains(p.getP2()) && a1.contains(p.getP1()) && a2.contains(p.getP2())) ||
|
||||
(a1.getBounds().contains(p.getP2()) && a2.getBounds().contains(p.getP1())) && a1.contains(p.getP2()) && a2.contains(p.getP1())) {
|
||||
if ((bounds1.contains(p.getP1()) && bounds2.contains(p.getP2()) && a1.contains(p.getP1()) && a2.contains(p.getP2())) ||
|
||||
(bounds1.contains(p.getP2()) && bounds2.contains(p.getP1())) && a1.contains(p.getP2()) && a2.contains(p.getP1())) {
|
||||
a1.add(a2);
|
||||
consumed[j] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bounds1.intersects(bounds2)) {
|
||||
candidates.put(a2, j);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Area> mergeQueue = new HashSet<Area>();
|
||||
|
||||
// run a parallel for-each to determine which areas overlap with a1 and add them to a queue to be added to a1 later
|
||||
if (candidates.size() > 0) {
|
||||
candidates.entrySet().parallelStream().forEach(x -> {
|
||||
Area intersection = new Area(a1);
|
||||
intersection.intersect(x.getKey());
|
||||
if (intersection != null && !intersection.isEmpty()) {
|
||||
synchronized (mergeQueue) {
|
||||
mergeQueue.add(x.getKey());
|
||||
}
|
||||
consumed[x.getValue()] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// if make a union of all intersecting areas
|
||||
for(Area a2 : mergeQueue)
|
||||
a1.add(a2);
|
||||
}
|
||||
|
||||
for (int i = 0; i < areas.size(); i++)
|
||||
if (!consumed[i])
|
||||
newAreas.add(areas.get(i));
|
||||
|
||||
Reference in New Issue
Block a user