Package nodebox.node

Source Code of nodebox.node.NodeContext

package nodebox.node;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import nodebox.function.Function;
import nodebox.function.FunctionRepository;
import nodebox.graphics.Point;
import nodebox.util.ListUtils;

import java.io.File;
import java.util.*;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

public final class NodeContext {

    private final NodeLibrary nodeLibrary;
    private final FunctionRepository functionRepository;
    private final ImmutableMap<String, ?> data;
    private final ImmutableMap<Node, List<?>> previousRenderResults;
    private final Map<Node, List<?>> renderResults;
    private final Map<NodeArguments, List<?>> nodeArgumentsResults;
    private final Map<String, ?> portOverrides;

    private static final ImmutableMap<String, ?> DEFAULT_CONTEXT_DATA = ImmutableMap.of("frame", 1.0);

    public NodeContext(NodeLibrary nodeLibrary) {
        this(nodeLibrary, null);
    }

    public NodeContext(NodeLibrary nodeLibrary, FunctionRepository functionRepository) {
        this(nodeLibrary, functionRepository, DEFAULT_CONTEXT_DATA, ImmutableMap.<Node, List<?>>of(), ImmutableMap.<String,Object>of());
    }

    public NodeContext(NodeLibrary nodeLibrary, FunctionRepository functionRepository, Map<String, ?> data) {
        this(nodeLibrary, functionRepository, data, ImmutableMap.<Node, List<?>>of(), ImmutableMap.<String,Object>of());
    }

    public NodeContext(NodeLibrary nodeLibrary, FunctionRepository functionRepository, Map<String, ?> data, Map<Node, List<?>> previousRenderResults, Map<String, ?> portOverrides) {
        this.nodeLibrary = nodeLibrary;
        this.functionRepository = functionRepository != null ? functionRepository : nodeLibrary.getFunctionRepository();
        this.data = ImmutableMap.copyOf(data);
        this.renderResults = new HashMap<Node, List<?>>();
        this.nodeArgumentsResults = new HashMap<NodeArguments, List<?>>();
        this.previousRenderResults = ImmutableMap.copyOf(previousRenderResults);
        this.portOverrides = ImmutableMap.copyOf(portOverrides);
    }

    public NodeLibrary getNodeLibrary() {
        return nodeLibrary;
    }

    public FunctionRepository getFunctionRepository() {
        return functionRepository;
    }

    public double getFrame() {
        try {
            return (Double) data.get("frame");
        } catch (ClassCastException e) {
            return 1.0;
        } catch (NullPointerException e) {
            return 1.0;
        }
    }

    public Map<String, ?> getData() {
        return data;
    }

    public Map<Node, List<?>> getRenderResults() {
        return renderResults;
    }

    /**
     * Render the node by rendering its rendered child, or the function.
     * Because it can't look at what network it is in, this function does not evaluate dependencies
     * of the node. However, if this node is a network, the whole child node is evaluated.
     *
     * @param node The node to render.
     * @return The list of results.
     * @throws NodeRenderException If processing fails.
     */
    public List<?> renderNode(Node node) throws NodeRenderException {
        return renderNode(node, Collections.<Port, Object>emptyMap());
    }

    public void renderAlwaysRenderedNodes(Node node) throws NodeRenderException {
        if (!node.isNetwork()) return;
        for (Node child : node.getChildren()) {
            if (child.isAlwaysRendered()) {
                if (!renderResults.containsKey(child))
                    renderNode(child);
            }
        }
    }

    public List<?> renderNode(Node node, Map<Port, ?> argumentMap) {
        checkNotNull(node);
        checkNotNull(functionRepository);

        // If the node has children, forgo the operation of the current node and evaluate the child.
        Object result;
        if (node.isNetwork()) {
            if (node.hasRenderedChild())
                result = renderChild(node, node.getRenderedChild(), argumentMap);
            else
                result = ImmutableList.of();
        } else {
            result = invokeNode(node, argumentMap);
        }
        List<?> results = postProcessResult(node, result);
        renderResults.put(node, results);
        return results;
    }

    private List<?> postProcessResult(Node node, Object result) {
        if (node.hasListOutputRange()) {
            // TODO This is a temporary fix for networks that have no rendered nodes.
            // They execute the "core/zero" function which returns a single value, not a list.
            if (result instanceof List<?>) {
                return (List<?>) result;
            } else {
                return ImmutableList.of(result);
            }
        } else if (result instanceof List) {
            List<?> results = (List<?>) result;
            if (results.isEmpty())
                return results;
            Class outputType = ListUtils.listClass(results);
            if (outputType.equals(Point.class) && node.getOutputType().equals("geometry"))
                return results;
        }
        return result == null ? ImmutableList.of() : ImmutableList.of(result);
    }

    public List<?> renderChild(Node network, Node child) throws NodeRenderException {
        return renderChild(network, child, Collections.<Port, Object>emptyMap());
    }

    public List<?> renderChild(Node network, Node child, Map<Port, ?> networkArgumentMap) {
        NodeArguments nodeArguments = new NodeArguments(network, child, networkArgumentMap);

        List<?> storedResults = nodeArgumentsResults.get(nodeArguments);
        if (storedResults != null) return storedResults;

        // A list of all result objects.
        List<Object> resultsList = new ArrayList<Object>();
        // If the node has no input ports, execute the node once for its side effects.
        if (child.getInputs().isEmpty()) {
            return renderNode(child);
        } else {
            // The list of values that need to be processed for this port.
            Map<Port, List<?>> portArguments = new LinkedHashMap<Port, List<?>>();

            // Evaluate the port data.
            for (Port port : child.getInputs()) {
                List<?> result = evaluatePort(network, child, port, networkArgumentMap);
                result = convertResultsForPort(port, result);
                result = clampResultsForPort(port, result);
                portArguments.put(port, result);
            }

            // Data from the network (through published ports) overrides the arguments.
            for (Map.Entry<Port, ?> argumentEntry : networkArgumentMap.entrySet()) {
                Port networkPort = argumentEntry.getKey();
                checkState(networkPort.isPublishedPort(), "Given port %s is not a published port.", networkPort);
                if (networkPort.getChildNode(network) == child) {
                    Port childPort = networkPort.getChildPort(network);
                    Object value = argumentEntry.getValue();
                    List<?> values;
                    if (value instanceof List) {
                        values = (List<?>) value;
                    } else {
                        values = ImmutableList.of(value);
                    }
                    portArguments.put(childPort, values);
                }
            }

            // A prepared list of argument lists, each for one invocation of the child node.
            Iterable<Map<Port, ?>> argumentMaps = buildArgumentMaps(portArguments);

            for (Map<Port, ?> argumentMap : argumentMaps) {
                List<?> results = renderNode(child, argumentMap);
                resultsList.addAll(results);
            }
        }
        nodeArgumentsResults.put(nodeArguments, resultsList);
        return resultsList;
    }

    private Object invokeNode(Node node, Map<Port, ?> argumentMap) {
        List<Port> inputs = node.getInputs();
        Object[] arguments = new Object[inputs.size()];
        int i = 0;
        for (Port port : inputs) {
            Object argument;
            if (argumentMap.containsKey(port)) {
                argument = argumentMap.get(port);
            } else if (port.hasValueRange()) {
                argument = getPortValue(node, port);
            } else {
                // The port expects a list but nothing is connected. Evaluate with an empty list.
                argument = ImmutableList.of();
            }
            arguments[i] = argument;
            i++;
        }
        return invokeNode(node, arguments);
    }

    private Object invokeNode(Node node, Object[] arguments) {
        Function function = functionRepository.getFunction(node.getFunction());
        return invokeFunction(node, function, arguments);
    }

    private List<?> convertResultsForPort(Port port, List<?> values) {
        Class outputType = ListUtils.listClass(values);

        // This is a special case: when working with geometry nodes, we may want to work with either
        // single IGeometry objects or a list of Point's.
        // In the latter case, we have to wrap these values in a list.
        if (outputType.equals(Point.class) && port.getType().equals("geometry") && port.hasValueRange())
            return ImmutableList.of(values);

        return TypeConversions.convert(outputType, port.getType(), values);
    }

    private List<?> clampResultsForPort(Port port, List<?> values) {
        if (port.getMinimumValue() == null && port.getMaximumValue() == null) return values;
        ImmutableList.Builder<Object> b = ImmutableList.builder();
        for (Object v : values) {
            b.add(port.clampValue(v));
        }
        return b.build();
    }

    private Node findOutputNode(Node network, Node inputNode, Port inputPort) {
        for (Connection c : network.getConnections()) {
            if (c.getInputNode().equals(inputNode.getName()) && c.getInputPort().equals(inputPort.getName())) {
                return network.getChild(c.getOutputNode());
            }
        }
        return null;
    }

    private List<?> evaluatePort(Node network, Node child, Port childPort, Map<Port, ?> networkArgumentMap) {
        Node outputNode = findOutputNode(network, child, childPort);
        if (outputNode != null) {
            List<?> result = renderChild(network, outputNode, networkArgumentMap);
            if (childPort.isFileWidget()) {
                return convertToFileNames(result);
            }
            return result;
        } else {
            Object value = getPortValue(child, childPort);
            if (value == null) {
                return ImmutableList.of();
            } else {
                return ImmutableList.of(value);
            }
        }
    }

    private List<?> convertToFileNames(List<?> values) {
        ImmutableList.Builder<Object> b = ImmutableList.builder();
        for (Object v : values) {
            b.add(convertToFileName(v));
        }
        return b.build();
    }

    private Object convertToFileName(Object value) {
        String path = String.valueOf(value);
        if (!path.startsWith("/")) {
            // Convert relative to absolute path.
            if (nodeLibrary.getFile() != null) {
                File f = new File(nodeLibrary.getFile().getParentFile(), path);
                return f.getAbsolutePath();
            }
        }
        return path;
    }

    /**
     * Get the value of the port.
     * <p/>
     * This method does some last-minute conversions and lookups on special cases:
     * <ul>
     * <li>If the port type is context, return a reference to the current node context.</li>
     * <li>If the port is a file widget, convert relative to absolute paths.</li>
     * </ul>
     */
    private Object getPortValue(Node node, Port port) {
        String portKey = node.getName() + "." + port.getName();
        Object overrideValue = portOverrides.get(portKey);
        Object portValue = overrideValue == null ? port.getValue() : overrideValue;
        if (port.getType().equals("context")) {
            return this;
        } else if (port.getType().equals(Port.TYPE_STATE)) {
            // The state of the node is the output value of the previous render of that node.
            Object previousState = previousRenderResults.get(node);
            if (previousState != null) {
                return previousState;
            } else {
                return ImmutableList.of();
            }
        } else if (port.isFileWidget() && !port.stringValue().isEmpty()) {
            return convertToFileName(portValue);
        }
        return portValue;
    }

    private Object invokeFunction(Node node, Function function, Object[] arguments) throws NodeRenderException {
        try {
            return function.invoke(arguments);
        } catch (Exception e) {
            throw new NodeRenderException(node, e);
        }
    }

    /**
     * Get the object of the list at the specified index.
     * <p/>
     * If the index is bigger than the list size, the index is wrapped.
     */
    private static Object wrappingGet(List<?> list, int index) {
        return list.get(index % list.size());
    }

    /**
     * Build the arguments to invoke a node with.
     * <p/>
     * Given the following lists per port:
     * {alpha:[1 2 3 4 5]
     * beta: ["a" "b"]
     * gamma: [true]}
     * <p/>
     * Builds the following argument maps:
     * [
     * {alpha: 1 beta:"a" gamma:true}
     * {alpha: 2 beta:"b" gamma:true}
     * {alpha: 3 beta:"a" gamma:true}
     * {alpha: 4 beta:"b" gamma:true}
     * {alpha: 5 beta:"a" gamma:true}]
     */
    private static Iterable<Map<Port, ?>> buildArgumentMaps(final Map<Port, List<?>> argumentsPerPort) {
        final int minSize = smallestArgumentList(argumentsPerPort);
        if (minSize == 0) return Collections.emptyList();

        final int maxSize = biggestArgumentList(argumentsPerPort);
        return new Iterable<Map<Port, ?>>() {
            int i = 0;

            @Override
            public Iterator<Map<Port, ?>> iterator() {
                return new Iterator<Map<Port, ?>>() {
                    @Override
                    public boolean hasNext() {
                        return i < maxSize;
                    }

                    @Override
                    public Map<Port, ?> next() {
                        Map<Port, Object> argumentMap = new HashMap<Port, Object>(argumentsPerPort.size());
                        for (Map.Entry<Port, List<?>> entry : argumentsPerPort.entrySet()) {
                            if (entry.getKey().hasListRange()) {
                                argumentMap.put(entry.getKey(), entry.getValue());
                            } else {
                                argumentMap.put(entry.getKey(), wrappingGet(entry.getValue(), i));
                            }
                        }
                        i++;
                        return argumentMap;
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }

    private static int smallestArgumentList(Map<Port, List<?>> argumentsPerPort) {
        int minSize = Integer.MAX_VALUE;
        for (Map.Entry<Port, List<?>> entry : argumentsPerPort.entrySet()) {
            minSize = Math.min(minSize, argumentListSize(entry.getKey(), entry.getValue()));
        }
        return minSize;
    }

    private static int biggestArgumentList(Map<Port, List<?>> argumentsPerPort) {
        int maxSize = 0;
        for (Map.Entry<Port, List<?>> entry : argumentsPerPort.entrySet()) {
            maxSize = Math.max(maxSize, argumentListSize(entry.getKey(), entry.getValue()));
        }
        return maxSize;
    }

    private static int argumentListSize(Port port, List<?> arguments) {
        // If the port takes in a list, he will always take the entire argument list as an argument.
        // Therefore, the size of arguments is 1.
        if (port.hasListRange()) {
            return 1;
        } else {
            return arguments.size();
        }
    }

    private class NodeArguments {
        private Node network;
        private Node node;
        private Map<Port, ?> argumentMap;

        public NodeArguments(Node network, Node node, Map<Port, ?> argumentMap) {
            this.network = network;
            this.node = node;
            this.argumentMap = argumentMap;
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(network, node, argumentMap);
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof NodeArguments)) return false;
            final NodeArguments other = (NodeArguments) o;
            return Objects.equal(network, other.network)
                    && Objects.equal(node, other.node)
                    && Objects.equal(argumentMap, other.argumentMap);
        }
    }
}
TOP

Related Classes of nodebox.node.NodeContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.