| /********************************************************************************* |
| * Copyright (c) 2020 Robert Bosch GmbH and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Robert Bosch GmbH - initial API and implementation |
| ******************************************************************************** |
| */ |
| package org.eclipse.app4mc.amalthea.visualizations.standard.util; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.stream.Collectors; |
| |
| import org.eclipse.app4mc.amalthea.model.Label; |
| import org.eclipse.app4mc.amalthea.model.LabelAccess; |
| import org.eclipse.app4mc.amalthea.model.Runnable; |
| import org.eclipse.app4mc.amalthea.model.SWModel; |
| import org.eclipse.app4mc.amalthea.model.util.SoftwareUtil; |
| |
| public final class RunnableHelper { |
| |
| private RunnableHelper() { |
| // empty private default constructor for helper class |
| } |
| |
| /** |
| * Typically textures with bigger dimensions than 8192 x 8192 can't even be |
| * processed by modern graphic cards. Since even that is to much in some cases, |
| * we specify an even smaller value. |
| */ |
| public static final double CANVAS_MAX = 6000; |
| |
| /** |
| * Calculates a grid out of the given number of items. |
| * |
| * @param count The number of items. |
| * @return A grid definition where the first entry in the array is the number of |
| * columns, the second is the number of rows. |
| */ |
| public static double[] calculateGrid(int count) { |
| // special handling for 3 |
| if (count == 3) { |
| return new double[] {3, 1}; |
| } |
| |
| double sqrt = Math.sqrt(count); |
| |
| double columns = Math.ceil(sqrt); |
| double rows = Math.max(1, Math.floor(sqrt)); |
| |
| double cells = columns * rows; |
| |
| if (cells < count) { |
| rows += 1; |
| } |
| |
| return new double[] {columns, rows}; |
| } |
| |
| /** |
| * Calculate the minimum grid cell width based on the minimum preferred |
| * dimensions of the given painter. |
| * |
| * @param grid The grid definition. |
| * @param painter The painter that should be rendered in the grid cells. |
| * @return The minimum cell dimensions where the first entry in the array is the |
| * cell width, the second entry is the cell height. |
| */ |
| public static double[] calculateMinimumCellDimensions(double[] grid, List<RunnablePainter> painter) { |
| double minWidth = 0; |
| double minHeight = 0; |
| int count = 0; |
| RunnablePainter p = null; |
| for (int row = 0; row < grid[1]; row++) { |
| for (int column = 0; column < grid[0]; column++) { |
| if (count >= painter.size()) { |
| break; |
| } |
| p = painter.get(count); |
| minWidth = Math.max(minWidth, p.getPreferredMinimumWidth()); |
| minHeight = Math.max(minHeight, p.getPreferredMinimumHeight()); |
| count++; |
| } |
| } |
| return new double[] {minWidth, minHeight}; |
| } |
| |
| /** |
| * Find all {@link Runnable} to which the given {@link Runnable} has |
| * dependencies in terms of Label access. |
| * |
| * @param swModel The {@link SWModel} needed to access all available |
| * {@link Runnable}. |
| * @param runnable The {@link Runnable} for which the dependencies |
| * should be resolved. |
| * @param collectTransitive <code>false</code> if only the {@link Runnable} |
| * should be collected that directly write to the given |
| * {@link Label}, <code>true</code> if the read |
| * dependencies of the found {@link Runnable} should be |
| * further evaluated. |
| * @return List of {@link Runnable} to which the given {@link Runnable} is |
| * dependent. |
| */ |
| public static List<Runnable> getRunnableDependencies(SWModel swModel, Runnable runnable, boolean collectTransitive) { |
| |
| List<Label> readItems = SoftwareUtil.getReadLabelAccessList(runnable, null).stream() |
| .map(LabelAccess::getData) |
| .collect(Collectors.toList()); |
| |
| List<Label> writeItems = SoftwareUtil.getWriteLabelAccessList(runnable, null).stream() |
| .map(LabelAccess::getData) |
| .collect(Collectors.toList()); |
| |
| // find all Runnables that write to labels that are read by the given Runnable |
| List<Runnable> dependencies = new ArrayList<>(); |
| collectReadDependencies(swModel, readItems, dependencies, collectTransitive); |
| Collections.reverse(dependencies); |
| |
| // first remove the starting point if it was added to the read dependencies |
| dependencies.remove(runnable); |
| |
| // now add it at the correct position |
| dependencies.add(runnable); |
| |
| // find all Runnables that read from labels that are written by the given Runnable |
| collectWriteDependencies(swModel, writeItems, dependencies, collectTransitive); |
| |
| return dependencies; |
| } |
| |
| /** |
| * Collect all {@link Runnable} that write to the given {@link Label}s. |
| * |
| * @param swModel The {@link SWModel} needed to access all available |
| * {@link Runnable}. |
| * @param readItems The {@link Label}s to check for. |
| * @param collected The collection of {@link Runnable} that are already |
| * collected. |
| * @param collectTransitive <code>false</code> if only the {@link Runnable} |
| * should be collected that directly write to the given |
| * {@link Label}, <code>true</code> if the read |
| * dependencies of the found {@link Runnable} should be |
| * further evaluated. |
| */ |
| public static void collectReadDependencies(SWModel swModel, List<Label> readItems, List<Runnable> collected, boolean collectTransitive) { |
| // find all Runnables that write to labels that are read by the given Runnable |
| List<Runnable> directReadDependency = swModel.getRunnables().stream() |
| .filter(r -> { |
| List<Label> wItems = SoftwareUtil.getWriteLabelAccessList(r, null).stream() |
| .map(LabelAccess::getData) |
| .collect(Collectors.toList()); |
| |
| for (Label l : readItems) { |
| if (wItems.contains(l)) { |
| return true; |
| } |
| } |
| |
| return false; |
| }) |
| .filter(item -> !collected.contains(item)) |
| .collect(Collectors.toList()); |
| |
| collected.addAll(directReadDependency); |
| |
| if (collectTransitive) { |
| directReadDependency.forEach(dependency -> { |
| List<Label> read = SoftwareUtil.getReadLabelAccessList(dependency, null).stream() |
| .map(LabelAccess::getData) |
| .collect(Collectors.toList()); |
| |
| collectReadDependencies(swModel, read, collected, collectTransitive); |
| }); |
| } |
| } |
| |
| /** |
| * Collect all {@link Runnable} that read from the given {@link Label}s. |
| * |
| * @param swModel The {@link SWModel} needed to access all available |
| * {@link Runnable}. |
| * @param writeItems The {@link Label}s to check for. |
| * @param collected The collection of {@link Runnable} that are already |
| * collected. |
| * @param collectTransitive <code>false</code> if only the {@link Runnable} |
| * should be collected that directly write to the given |
| * {@link Label}, <code>true</code> if the read |
| * dependencies of the found {@link Runnable} should be |
| * further evaluated. |
| */ |
| public static void collectWriteDependencies(SWModel swModel, List<Label> writeItems, List<Runnable> collected, boolean collectTransitive) { |
| // find all Runnables that read from labels that are written by the given Runnable |
| List<Runnable> directWriteDependency = swModel.getRunnables().stream() |
| .filter(r -> { |
| List<Label> rItems = SoftwareUtil.getReadLabelAccessList(r, null).stream() |
| .map(LabelAccess::getData) |
| .collect(Collectors.toList()); |
| |
| for (Label l : writeItems) { |
| if (rItems.contains(l)) { |
| return true; |
| } |
| } |
| |
| return false; |
| }) |
| .filter(item -> !collected.contains(item)) |
| .collect(Collectors.toList()); |
| |
| collected.addAll(directWriteDependency); |
| |
| if (collectTransitive) { |
| directWriteDependency.forEach(dependency -> { |
| List<Label> write = SoftwareUtil.getWriteLabelAccessList(dependency, null).stream() |
| .map(LabelAccess::getData) |
| .collect(Collectors.toList()); |
| |
| collectWriteDependencies(swModel, write, collected, collectTransitive); |
| }); |
| } |
| } |
| |
| /** |
| * Calculates the maximum scale factor to be used to avoid texture overflows on |
| * rendering. |
| * |
| * @param painter The list of {@link RunnablePainter} needed for calculation of |
| * the grid and the minimum cell dimensions. |
| */ |
| public static void setMaxScaleFactor(List<RunnablePainter> painter) { |
| setMaxScaleFactor(painter, RunnableHelper.calculateGrid(painter.size())); |
| } |
| |
| /** |
| * Calculates the maximum scale factor to be used to avoid texture overflows on |
| * rendering. |
| * |
| * @param painter The list of {@link RunnablePainter} needed for calculation of |
| * the minimum cell dimensions. |
| * @param grid The grid definition needed for calculation of the minimum cell |
| * dimensions. |
| */ |
| public static void setMaxScaleFactor(List<RunnablePainter> painter, double[] grid) { |
| double[] minDim = RunnableHelper.calculateMinimumCellDimensions(grid, painter); |
| |
| // calculate the maximum scaling factor |
| // because textures with bigger dimensions than 8192 x 8192 can't even be processed by modern graphic cards |
| double max = Math.max(grid[0] * minDim[0], grid[1] * minDim[1]); |
| double factor = CANVAS_MAX / max; |
| painter.forEach(p -> p.setMaxScaleFactor(factor)); |
| } |
| } |