Compare commits

...

3 Commits

Author SHA1 Message Date
Nekojimi 97301d13f1 Update README.md. 2023-02-25 19:54:03 +00:00
Nekojimi dee5a39bcf Merged port and link behaviour into only one type. 2022-07-20 23:49:32 +01:00
Nekojimi f4e9c3e823 Initial version of a lot of behaviour. 2022-07-17 23:02:04 +01:00
17 changed files with 1441 additions and 3 deletions

176
README.md
View File

@ -1,2 +1,176 @@
# JavaMavenTemplate # Inner Platform (Working Title)
A visual graph-based hacking language made for stupid purposes.
## Applications
This repository contains the source for three applications:
`inner-platform`, the runtime for executing programs
`inner-server`, a daemon for managing, debugging, and deploying your programs
`inner-editor`, a GUI application for editing programs
## Specification
A basic and rubbish spec for the Inner Platfom Hacking Language (IPHL)
### Objects & Concepts
The executable unit of IPHL code is the **program**, which lives in a .iphp file. Non-executable code (i.e libraries) is a **module** which lives in a .iphm file.
These are mostly the same and so the word program will be used for both cases; maybe I'll have to come up with a generic name for both at some point.
A program is made up of **nodes** and **pipes**. A node represents a function, object, or other thing-that-does-processing. A **pipe** carries **values** in one direction between nodes, and also carry **signals** in either direction which transfer control.
Each node has a number of **ports**, which can be either input or output ports. A pipe always connects one output port to one input port. Normally, any port may have zero to many pipes connected to it, although some nodes may impose a uniqueness rule on their ports (see Ports).
#### Nodes
A node's purpose is to take input from it's input ports, process it, and send it to the output ports. It may also communicate with other sources it owns (such as a websocket, external process), although it should not cause side effects that are observable from other nodes, unless the programmer sets this up deliberately.
##### Representation
A node is visually represented by a box; at the top of the box, probably centred, is it's name. This top region may also be used for icons or buttons; the most important for editors is probably a little X button on the top-right, which can be used to remove a node from the program.
Below that is the node's ports (see Ports/Representation); input ports are down the left edge, and output ports are down the right edge. Next to each port (towards the inside) are port labels.
##### Properties
Things a node has:
- A name, which can be changed but defaults to a value
- A type, which is one of the available node types, see below
- Zero or more input ports
- Zero or more output ports
- A reset function, which clears any transient state from a node
- A check function, which tests if a node has enough input data for it's process function to complete
- A process function, which takes values from the input ports and turns them into values in the output ports.
Nodes should have at least one port (of either type) otherwise they're useless.
#### Ports
Ports are part of a node, and represent an input or output of data values. All ports have a name (unique within their parent node) and a type (the data type of the values the port will accept or generate).
##### Representation
A port is visually represented by a small shape, such as a circle. This is drawn on the left or right edge of the port's node; left side for input ports, and right side for output ports. The port shape can touch or overlap the edge, or may be distant from it, whichever you'd prefer.
The colour of a port represents it's type, a listing of suggested colours is below:
- Red: boolean values
- Orange: signal only (see Pipes)
- Blue: integers
- Cyan: floats
- Green: strings
- Yellow: complex objects
- Black/white (depending which way up your colour scheme is): any type, or unknown
The exact colours used is implementation-defined, but it is advised that suitability for colourblind persons (like me!) be considered when choosing them; ensure that colours vary in all three of hue, saturation, and lightness.
If a port's type is an array (or collection, etc), it's colour is that of the array type, but the port icon is double-lined. If it's an array of arrays, who bloody knows what happens, really (more lines?).
Next to a port's shape is the port label. Port labels show the name of a port. Input port labels are left-aligned, and vice versa.
In editor programs, port labels may be replaced with other components that represent a port in a more useful way, such as small text boxes that allow a port's value to be displayed.
##### Behaviour
Output ports give values to pipes, and input ports take values from pipes.
#### Pipes
Pipes connect output ports to input ports.
##### Representation
A pipe is represented by a continuous line, starting at the output port, ending at the input port, and drawn on top of both ports and nodes. How it gets from the start to the finish is implementation-defined.
Pipes are coloured according to their type; they have the same colour as the output port they are connected to (see Ports/Representation) They may vary their shade a little to make them easier to distinguish, or dynamically according to the value they contain, if you want.
##### Behaviour
Pipes need to implement at least two functions; an available function, which determines if a value can be read from the pipe (without blocking), and a read function which actually does the read.
### Program Flow
#### Node Deque
Internally, each program thread maintains a deque of nodes. At each step of the program, the node at the front of the deque is removed, and this node is processed; it reads or takes values from its input ports, and writes new values to its output ports. When a new value is written to an output port, a signal is sent through any connected pipes.
#### Operation order
Whenever operations on nodes don't specify an order, *dependency ordering* is to be used.
Dependency ordering is a partial ordering, which is defined by example as follows;
- If the outputs of a node A are connected to the inputs of a node B, then B is dependent on A.
- If C depends on B, and B depends on A, then C depends on A, too.
- If Y depends on X, but X doesn't depend on Y, then Y is after X in dependency ordering.
- If X and Y both depend on other, they are not comparible in dependency ordering.
- If neither X nor Y depend on each other (they aren't connected at all), they're not comparible in dependency ordering.
If two nodes end up not directly comparable, they are handled in an arbitary order.
To illustrate, consider the following situation:
- A descends from nothing
- B and C both descend from A
- D descends from both B and C
- E descends from A and B
- C descends from F, and F descends from C
B
/ \
A D
\ /
C
Then dependency ordering might go like this:
A > B > E > C > D > F
All this might not be necessary but it's provided as a hedge until I can work out it's not; the signalling-and-checking process might be sufficent to prevent inconsistency.
#### Signalling
To transfer control, nodes have a process called **signalling**; when a node is signalled, it calls its check function to see if there are enough values at the input ports to be processed (see Checking). If so, this node is added to the node deque, normally at the back.
A node is signalled under the following conditions:
- When a signal arrives at an input port from another node, indicating that a new value is available in the pipe.
- When another node wants to pull a value from a pull-port of this node. **Note that** in this case, the signalled node goes to the front of the node deque, not the back.
- Whenever a node has just finished processing, that same node is signalled again.
- A node may signal itself when an object it controls (running in another thread) receives new data. For example, a node implementing a bot on an IM program will signal itself when a new message arrives; a node representing a button will signal itself when the button is pressed.
- At the start of the program, the start nodes are signalled.
#### Checking
A node's check function returns a boolean value, indicating if there are enough input values at it's ports to allow the node to be processed. Generally, nodes will implement one of two sets of behaviour:
- **Function-like** nodes will have a checking function that requires that *all* input ports with a connected pipe must have a value ready. They may define some input ports as optional, meaning that processing can occur if there is no pipe connected to that port, but if there is a pipe then a value is needed. An optional port has its name displayed in (brackets).
- **Object-like** nodes will have a checking function that requires only a value at *any* connected input port. These will typically represent an object's getter methods or similar one-argument functions.
#### Program Start
At the start of the program, the following things happen in order:
- All nodes are asked to reset; this means to clear any volatile state information they hold. Nodes may keep persistent state between runs of the program, but only if this is an obvious part of their function.
- Start nodes are signalled in an arbitrary order.
### File Types
IPHL code is stored in YAML format, although with renamed file extensions. Programs themselves are stored in .iphp files (for Inner Platform Hacking Program) while modules (parts of programs) are stored in .iphm files (Inner Platform Hacking Module). Besides their different contents, the two file types are identical.
The exact structure of these files isn't defined yet, but each one is composed of a number of sections. Most sections are optional; if a IPHL editor doesn't know what to do with an section, it should pass it through without touching it.
- shebang: files always begin with #!/bin/innerplatform, which identifies them to the shell. This is also a YAML comment.
- `metadata:` contains metadata: what this file is, who made it, and so on.
- `name:` (string) the name of this file. It should be the same as the file name if possible.
- `author:` (string) who made the file. This is also used as the namespace. When storing module files on the disk, they are grouped into directories by author name.
- `license`: (string) the name of the license the file is distributed under. The default value is WTFPL.
- `code:` contains the actual nodes and pipes. This is the only mandatory section.
- `layout:` contains layout information: chiefly, where the nodes are positioned on screen, and what routes the pipes take.

32
nbactions.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action>
<actionName>run</actionName>
<packagings>
<packaging>jar</packaging>
</packagings>
<goals>
<goal>process-classes</goal>
<goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal>
</goals>
<properties>
<exec.args>-classpath %classpath moe.nekojimi.nodeprocessor.Main</exec.args>
<exec.executable>java</exec.executable>
</properties>
</action>
<action>
<actionName>debug</actionName>
<packagings>
<packaging>jar</packaging>
</packagings>
<goals>
<goal>process-classes</goal>
<goal>org.codehaus.mojo:exec-maven-plugin:1.5.0:exec</goal>
</goals>
<properties>
<exec.args>-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} -classpath %classpath moe.nekojimi.nodeprocessor.Main</exec.args>
<exec.executable>java</exec.executable>
<jpda.listen>true</jpda.listen>
</properties>
</action>
</actions>

13
pom.xml
View File

@ -35,4 +35,17 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<dependencies>
<dependency>
<groupId>com.amihaiemil.web</groupId>
<artifactId>eo-yaml</artifactId>
<version>5.2.3</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>
</dependencies>
</project> </project>

View File

@ -0,0 +1,30 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor;
/**
*
* @author jimj316
*/
public enum ClassMethodType
{
/**
* takes no input, makes no changes, returns a value
* Should be added to an object node, given a constant port
*/
GETTER,
SETTER, // takes one input, changes one field, returns nothing
ADDER, // takes one input, makes other state changes, returns nothing
TAKER, // takes no input, makes other state changes, returns a value
CALLBACK, // takes a single argument of an abstract class
ACTION, // takes no input or output
TRANSFORMER, // takes input, changes the object, then returns the object (eg. builder methods)
FUNCTION, // takes one or more unrelated types, and returns a result
CONSTANT, // static; no input, returns a value
FACTORY,
OTHER,
}

View File

@ -1,16 +1,190 @@
package moe.nekojimi.nodeprocessor; package moe.nekojimi.nodeprocessor;
import java.io.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.stream.Collectors;
import moe.nekojimi.nodeprocessor.links.Link;
import moe.nekojimi.nodeprocessor.nodes.MethodNode;
import moe.nekojimi.nodeprocessor.nodes.DebugOutNode;
import moe.nekojimi.nodeprocessor.nodes.ValueNode;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
/** /**
* *
* @author Nekojimi * @author Nekojimi
*/ */
public class Main public class Main
{ {
private static final Class[] testClasses =
{
String.class,
Integer.class,
Math.class,
HashMap.class,
OffsetDateTime.class,
};
/** /**
* @param args the command line arguments * @param args the command line arguments
*/ */
public static void main(String[] args) public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, IOException
{ {
System.out.println("Hello world!"); for (Class testClass : testClasses)
{
System.out.println(testClass.getName() + ":");
Method[] methods = testClass.getMethods();
for (Method method : methods)
{
if (method.getDeclaringClass() == testClass)
{
ClassMethodType type = guessMethodType(method);
System.out.println("\t" + method.toString());
System.out.println("\t\t" + type.name());
}
}
}
// helloWorldProgram();
} }
private static void fibonnaciProgram()
{
Program program = new Program();
}
private static void addNumbersProgram() throws NoSuchMethodException
{
Program program = new Program();
ValueNode<Integer> twoNode = new ValueNode<>(program, Integer.class);
twoNode.setValue(2);
ValueNode<Integer> threeNode = new ValueNode<>(program, Integer.class);
threeNode.setValue(3);
MethodNode<Integer> methodNode = new MethodNode<>(program, Integer.class, Integer.class.getMethod("sum", Integer.class, Integer.class));
DebugOutNode debugOutNode = new DebugOutNode(program);
// Link.makeLink(Integer.class, twoNode, fromPort, twoNode, toPort);
program.start();
}
private static void helloWorldProgram() throws SecurityException, NoSuchMethodException
{
Program program = new Program();
ValueNode<String> valueNode = new ValueNode<>(program, String.class);
valueNode.setValue("Hello world!");
final Method method = String.class.getMethod("toUpperCase");
MethodNode<String> methodNode = new MethodNode<>(program, String.class, method);
DebugOutNode debugOutNode = new DebugOutNode(program);
Link.makeLink(String.class, valueNode, "out", methodNode, "object");
Link.makeLink(String.class, methodNode, "out", debugOutNode, "in");
program.start();
}
public static Set<Class> findAllClassesUsingClassLoader(String packageName)
{
InputStream stream = ClassLoader.getSystemClassLoader()
.getResourceAsStream(packageName.replaceAll("[.]", "/"));
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
return reader.lines()
.filter(line -> line.endsWith(".class"))
.map(line -> getClass(line, packageName))
.collect(Collectors.toSet());
}
public static Set<Class> findAllClassesUsingReflectionsLibrary(String packageName)
{
Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
return reflections.getSubTypesOf(Object.class)
.stream()
.collect(Collectors.toSet());
}
private static Class getClass(String className, String packageName)
{
try
{
return Class.forName(packageName + "."
+ className.substring(0, className.lastIndexOf('.')));
} catch (ClassNotFoundException e)
{
// handle the exception
}
return null;
}
private static ClassMethodType guessMethodType(Method method)
{
String name = method.getName();
Class<?> clazz = method.getDeclaringClass();
Class<?> returnType = method.getReturnType();
boolean hasReturn = (returnType != Void.TYPE);
int paramCount = method.getParameterCount();
Class<?> firstParamType = null;
Parameter[] parameters = method.getParameters();
if (paramCount > 0)
firstParamType = parameters[0].getType();
boolean isStatic = Modifier.isStatic(method.getModifiers());
if (!isStatic)
{
if (hasReturn)
{
if (paramCount == 0) // methods that only return
{
if (name.startsWith("take") || name.startsWith("poll") || name.startsWith("remove") || name.startsWith("pop") || name.startsWith("read"))
return ClassMethodType.TAKER;
else
return ClassMethodType.GETTER;
} else if (clazz.isAssignableFrom(returnType))
return ClassMethodType.TRANSFORMER;
else if (paramCount == 1
&& Modifier.isAbstract(parameters[0].getType().getModifiers())
&& !parameters[0].getType().isPrimitive()
&& name.toLowerCase().matches("(on.+|.*listener.*|.*observer.*)"))
{
int abstractMethods = 0;
Method[] paramTypeMethods = parameters[0].getType().getMethods();
for (Method paramTypeMethod : paramTypeMethods)
{
if (Modifier.isAbstract(paramTypeMethod.getModifiers()))
abstractMethods++;
}
if (abstractMethods >= 1)
return ClassMethodType.CALLBACK;
} else
return ClassMethodType.FUNCTION;
} else // returns nothing
{
if (paramCount == 1)
{
if (name.startsWith("give") || name.startsWith("add") || name.startsWith("push") || name.startsWith("write"))
return ClassMethodType.ADDER;
else
return ClassMethodType.SETTER;
} else if (paramCount == 0) // no input, return nothing
return ClassMethodType.ACTION;
}
} else // static method
{
if (clazz.isAssignableFrom(returnType))
return ClassMethodType.FACTORY;
else if (hasReturn && paramCount > 0)
return ClassMethodType.FUNCTION;
else if (hasReturn && paramCount == 0)
return ClassMethodType.CONSTANT;
}
return ClassMethodType.OTHER;
}
} }

View File

@ -0,0 +1,36 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor;
import com.amihaiemil.eoyaml.YamlMapping;
import moe.nekojimi.nodeprocessor.nodes.Node;
/**
* Specifies a type of node that can exist.
* Holds a list of one or more methods and their archetypes to be added to the
* new node???
*
* @author jimj316
*/
public class NodeSpec<T extends Node>
{
public static NodeSpec fromYAML(YamlMapping yaml) throws ClassNotFoundException
{
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass(yaml.string("class"));
final NodeSpec nodeSpec = new NodeSpec(clazz);
return nodeSpec;
}
public NodeSpec(Class<T> clazz)
{
}
public T construct()
{
return null;
}
}

View File

@ -0,0 +1,108 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor;
import java.util.*;
import moe.nekojimi.nodeprocessor.links.Link;
import moe.nekojimi.nodeprocessor.nodes.Node;
/**
*
* @author jimj316
*/
public class Program
{
private final List<Node> nodes = new ArrayList<>();
private final Set<Link> links = new HashSet<>();
private final Queue<Node> executionQueue = new LinkedList<>();
private final Set<Node> idleNodes = new HashSet<>();
private Thread thread;
private State state = State.STOPPED;
public void addNode(Node node)
{
if (!nodes.contains(node))
nodes.add(node);
nodes.sort(Node.DEPENDENCY_ORDER);
}
public void addLink(Link link)
{
links.add(link);
}
public void queueNodeProcess(Node node)
{
executionQueue.add(node);
}
public void setIdleNode(Node node, boolean idle)
{
if (idle)
idleNodes.add(node);
else
idleNodes.remove(node);
}
public State getState()
{
return state;
}
public void start()
{
if (state != State.STOPPED)
throw new IllegalStateException("Thread isn't stopped!");
if (thread == null)
thread = new Thread(runnable);
thread.start();
}
private final Runnable runnable = () ->
{
state = State.RUNNING;
for (Node node : nodes)
{
node.reset();
if (node.getNumberOfInputs() == 0)
node.signal();
}
while (!executionQueue.isEmpty())
{
Node node = executionQueue.poll();
boolean done = node.process();
if (!done)
queueNodeProcess(node);
}
state = State.STOPPED;
};
public List<Node> getNodes()
{
return nodes;
}
public Set<Link> getLinks()
{
return links;
}
public Set<Node> getIdleNodes()
{
return idleNodes;
}
public enum State
{
STOPPED,
PAUSED,
RUNNING,
IDLE
}
}

View File

@ -0,0 +1,108 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.links;
import java.util.ArrayDeque;
import java.util.Queue;
import moe.nekojimi.nodeprocessor.nodes.Node;
import moe.nekojimi.nodeprocessor.ports.InPort;
import moe.nekojimi.nodeprocessor.ports.OutPort;
/**
*
* @author jimj316
*/
public class Link<T, I extends InPort<T>, O extends OutPort<T>>
{
final O from;
private final I to;
private final Queue<T> queue = new ArrayDeque<>(1);
public Link(O from, I to)
{
this.from = from;
this.to = to;
connect();
}
public final void connect()
{
from.connect(this);
to.connect(this);
from.getNode().getProgram().addLink(this);
to.getNode().getProgram().addLink(this);
}
public final void disconnect()
{
from.disconnect(this);
to.disconnect(this);
}
/**
* Attempt to take a value from the link
*
* @return
*/
public T read()
{
return queue.poll();
}
public void write(T val)
{
queue.add(val);
push();
}
public boolean has()
{
return !queue.isEmpty();
}
public boolean pull()
{
from.pull();
return has();
}
protected void push()
{
to.signal();
}
public static <T> Link<T, ?, ?> makeLink(Class<T> clazz, Node from, String fromPort, Node to, String toPort)
{
OutPort<T> outPort = from.getOutPort(fromPort, clazz);
InPort<T> inPort = to.getInPort(toPort, clazz);
// if (outPort instanceof QueueOutPort)
// {
// QueueOutPort<T> q = (QueueOutPort<T>) outPort;
// return new QueueLink<>(q, inPort);
// } else if (outPort instanceof ConstantOutPort)
// {
// ConstantOutPort<T> c = (ConstantOutPort<T>) outPort;
// return new ConstantLink<>(c, inPort);
// } else
// {
// return null;
// }
return new Link<>(outPort, inPort);
}
public O getFrom()
{
return from;
}
public I getTo()
{
return to;
}
}

View File

@ -0,0 +1,35 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.nodes;
import moe.nekojimi.nodeprocessor.Program;
import moe.nekojimi.nodeprocessor.ports.InPort;
public class DebugOutNode extends Node
{
public DebugOutNode(Program scene)
{
super(scene);
addInPort(new InPort("in", Object.class, this));
}
@Override
public boolean process()
{
InPort<Object> inPort = getInPort("in", Object.class);
System.out.println(inPort.get());
return true;
}
@Override
public boolean check()
{
InPort<Object> inPort = getInPort("in", Object.class);
return inPort.has();
}
}

View File

@ -0,0 +1,91 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.nodes;
import moe.nekojimi.nodeprocessor.ports.InPort;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import moe.nekojimi.nodeprocessor.Program;
import moe.nekojimi.nodeprocessor.ports.OutPort;
/**
*
* @author jimj316
* @param <C> the class that declares this method.
*/
public class MethodNode<C> extends Node
{
private final InPort<C> objectInPort;
private final OutPort returnOutPort;
private final List<InPort> argInPorts;
private final Method method;
public MethodNode(Program scene, Class<C> clazz, Method method)
{
super(scene);
assert (method.getDeclaringClass() == clazz);
argInPorts = new ArrayList<>();
if (Modifier.isStatic(method.getModifiers()))
objectInPort = null;
else
{
objectInPort = new InPort<>("object", clazz, this);
addInPort(objectInPort);
}
this.method = method;
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters)
{
final InPort inPort = new InPort(parameter.getName(), parameter.getType(), this);
argInPorts.add(inPort);
addInPort(inPort);
}
Class<?> returnType = method.getReturnType();
returnOutPort = new OutPort("out", returnType, this);
addOutPort(returnOutPort);
setName(method.getDeclaringClass().getSimpleName() + " " + method.getName());
}
@Override
public boolean process()
{
try
{
C object = null;
if (objectInPort != null)
object = objectInPort.get();
Object[] args = new Object[argInPorts.size()];
for (int i = 0; i < args.length; i++)
args[i] = argInPorts.get(i).get();
Object ret = method.invoke(object, args);
returnOutPort.set(ret);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
{
Logger.getLogger(MethodNode.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}
@Override
public boolean check()
{
return getInPorts().stream().allMatch(in -> (in.has()));
}
}

View File

@ -0,0 +1,183 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.nodes;
import java.util.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import moe.nekojimi.nodeprocessor.ports.InPort;
import moe.nekojimi.nodeprocessor.ports.OutPort;
import moe.nekojimi.nodeprocessor.Program;
import moe.nekojimi.nodeprocessor.ports.Port;
/**
*
* @author jimj316
*/
public abstract class Node
{
// protected Map<String, Link> inLinks = new HashMap<>();
// protected Map<String, Link> outLinks = new HashMap<>();
private final Program program;
private Map<String, InPort> inPorts = new HashMap<>();
private Map<String, OutPort> outPorts = new HashMap<>();
private String name;
public Node(Program program)
{
this.program = program;
program.addNode(this);
this.name = this.getClass().getSimpleName();
}
public String getName()
{
return name;
}
protected void setName(String name)
{
this.name = name;
}
protected final void addInPort(InPort port)
{
inPorts.putIfAbsent(getPortName(port.getClazz(), port.getName()), port);
}
protected final void addOutPort(OutPort port)
{
outPorts.putIfAbsent(getPortName(port.getClazz(), port.getName()), port);
}
public final <X> InPort<X> getInPort(String name, Class<X> clazz)
{
return (InPort<X>) getPort(name, clazz, inPorts);
}
public final <X> OutPort<X> getOutPort(String name, Class<X> clazz)
{
return (OutPort<X>) getPort(name, clazz, outPorts);
}
private final <X> Port<X> getPort(String name, Class<X> clazz, Map<String, ? extends Port> ports)
{
Class<? super X> c = clazz;
Port ret;
do
{
ret = ports.get(getPortName(c, name));
c = c.getSuperclass();
} while (ret == null && c != null);
if (ret == null)
if (clazz.isPrimitive())
return getPort(name, getBoxedType(clazz), ports);
else
portFindError(name, clazz, ports);
return ret;
}
private static final void portFindError(String name, Class clazz, Map<String, ? extends Port> ports)
{
String portList = ports.keySet().stream().collect(Collectors.joining(", "));
throw new IllegalArgumentException("Cannot find port with identity " + getPortName(clazz, name) + ", possibilities are: " + portList);
}
public final Collection<InPort> getInPorts()
{
return inPorts.values();
}
public final Collection<OutPort> getOutPorts()
{
return outPorts.values();
}
private static <X> String getPortName(Class<X> clazz, String name)
{
return clazz.getCanonicalName() + "-" + name;
}
public int getNumberOfInputs()
{
return inPorts.values().stream().mapToInt((t) -> t.getNumberOfConnections()).sum();
}
public int getNumberOfOutputs()
{
return outPorts.values().stream().mapToInt((t) -> t.getNumberOfConnections()).sum();
}
public Program getProgram()
{
return program;
}
public boolean dependsOn(Node other)
{
// TODO: this is a crude search that will enter an infinite loop if a node depends on itself
for (InPort port : inPorts.values())
{
Set<Node> linkedNodes = port.getLinkedNodes();
if (linkedNodes.contains(other))
return true;
else
for (Node dependent : linkedNodes)
{
if (dependent.dependsOn(other))
return true;
}
}
return false;
}
/**
* Processes this node's inputs, and generates output (or performs some
* other useful function).
*
* @return true if there will be no more output until an input is received;
* false if process() should be called again.
*/
public abstract boolean process();
public abstract boolean check();
public void signal()
{
if (check())
program.queueNodeProcess(this);
}
public void reset()
{
// assume most nodes are stateless
// default implementation does nothing
}
public static final Comparator<Node> DEPENDENCY_ORDER = (Node a, Node b) ->
{
int ret = 0;
if (a.dependsOn(b))
ret--;
if (b.dependsOn(a))
ret++;
return ret;
};
private static final Class getBoxedType(Class clazz)
{
if (clazz == int.class)
return Integer.class;
else if (clazz == long.class)
return Long.class;
else if (clazz == byte.class)
return Byte.class;
else
throw new IllegalArgumentException("wtf");
}
}

View File

@ -0,0 +1,159 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.nodes;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import moe.nekojimi.nodeprocessor.Program;
import moe.nekojimi.nodeprocessor.ports.InPort;
import moe.nekojimi.nodeprocessor.ports.OutPort;
/**
* Represents an object that performs some function.
* Has a set of inputs that will cause the object to be (re) created when
* changed.
*
* @author jimj316
* @param <T>
*/
public class ObjectNode<T> extends Node
{
protected T object;
private final Constructor<T> constructor;
private final Set<InPort> composingInPorts = new HashSet<>();
private final Set<InPort> setterInPorts = new HashSet<>();
private final Set<OutPort> getterOutPorts = new HashSet<>();
private final OutPort<T> objectOutPort;
private final Map<String, Object> composingValues = new HashMap<>();
private final Class<T> clazz;
public ObjectNode(Program scene, Class<T> clazz, Constructor<T> constructor)
{
super(scene);
this.clazz = clazz;
this.constructor = constructor;
objectOutPort = new OutPort<>("this", clazz, this);
addConstructor();
addAutoMethods();
}
public void addAutoMethods() throws SecurityException
{
for (Method method : clazz.getMethods())
{
String name = method.getName();
Class<?> returnType = method.getReturnType();
Parameter[] parameters = method.getParameters();
if (returnType == null && parameters.length == 1 && name.startsWith("set"))
{
// this is a setter method
InPort in = new InPort(name, parameters[0].getType(), this);
addInPort(in);
setterInPorts.add(in);
} else if (returnType != null && parameters.length == 0 && (name.startsWith("get") || name.startsWith("is")))
{
// this is a getter method
OutPort out = new OutPort(name, returnType, this);
addOutPort(out);
getterOutPorts.add(out);
}
}
}
public void addConstructor()
{
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter : parameters)
{
InPort in = new InPort(parameter.getName(), parameter.getType(), this);
addInPort(in);
composingInPorts.add(in);
}
}
@Override
public void reset()
{
super.reset();
if (object instanceof Closeable)
{
try
{
((Closeable) object).close();
} catch (IOException ex)
{
Logger.getLogger(ObjectNode.class.getName()).log(Level.SEVERE, null, ex);
}
}
object = null;
}
@Override
public boolean process()
{
// check if the composing values have changed; if so delete the object
// if the object doesn't exist, create it
if (object == null)
{
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors)
{
Parameter[] parameters = constructor.getParameters();
List<Object> args = new ArrayList<>();
for (Parameter parameter : parameters)
{
InPort<?> inPort = getInPort(parameter.getName(), parameter.getType());
if (inPort.has())
args.add(inPort.get());
else
break;
}
if (args.size() == parameters.length)
{
try
{
object = (T) constructor.newInstance(args.toArray());
objectOutPort.set(object);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
{
Logger.getLogger(ObjectNode.class.getName()).log(Level.SEVERE, null, ex);
}
break;
}
}
}
// now the object exists, do something with it
if (object != null)
{
// take input from setters
// give output to getters
return doProcess();
}
return true;
}
protected boolean doProcess()
{
return true;
}
@Override
public boolean check()
{
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}

View File

@ -0,0 +1,56 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.nodes;
import java.util.Iterator;
import moe.nekojimi.nodeprocessor.Program;
import moe.nekojimi.nodeprocessor.ports.InPort;
import moe.nekojimi.nodeprocessor.ports.OutPort;
public class SerialiserNode<T> extends Node
{
private final Class<Iterable<T>> aClass;
private final Class<T> clazz;
public SerialiserNode(Program scene, Class<T> clazz)
{
super(scene);
Iterable<T> it = () ->
{
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
};
aClass = (Class<Iterable<T>>) it.getClass();
addInPort(new InPort("in", aClass, this));
this.clazz = clazz;
addOutPort(new OutPort("out", clazz, this));
}
@Override
public boolean process()
{
InPort<Iterable<T>> inPort = getInPort("in", aClass);
OutPort<T> outPort = getOutPort("out", clazz);
if (inPort.has())
{
Iterable<T> iterable = inPort.get();
Iterator<T> iterator = iterable.iterator();
while (iterator.hasNext())
{
outPort.set(iterator.next());
}
}
return true;
}
@Override
public boolean check()
{
InPort<Iterable<T>> inPort = getInPort("in", aClass);
return inPort.has();
}
}

View File

@ -0,0 +1,55 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.nodes;
import moe.nekojimi.nodeprocessor.Program;
import moe.nekojimi.nodeprocessor.ports.InPort;
import moe.nekojimi.nodeprocessor.ports.OutPort;
public class ValueNode<T> extends Node
{
private T value;
private final OutPort<T> outPort;
private final InPort<T> inPort;
public ValueNode(Program scene, Class<T> clazz)
{
super(scene);
inPort = new InPort("set", clazz, this);
addInPort(inPort);
outPort = new OutPort("out", clazz, this);
addOutPort(outPort);
}
public void setValue(T val)
{
value = val;
// signal();
}
public T getValue()
{
return value;
}
@Override
public boolean process()
{
if (inPort.has())
value = inPort.get();
outPort.set(value);
return true;
}
@Override
public boolean check()
{
return true;
}
}

View File

@ -0,0 +1,51 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.ports;
import moe.nekojimi.nodeprocessor.links.Link;
import moe.nekojimi.nodeprocessor.nodes.Node;
/**
*
* @author jimj316
*/
public class InPort<T> extends Port<T>
{
public InPort(String name, Class<T> clazz, Node node)
{
super(name, clazz, node);
}
public boolean has()
{
if (links.isEmpty())
return false;
else
return links.stream().anyMatch((l) -> l.has());
}
public T get()
{
for (Link<T, ?, ?> link : links)
{
if (link.has())
return link.read();
}
for (Link<T, ?, ?> link : links)
{
link.pull(); // push the from side to prompt it to give us a value; it goes in the queue
node.signal(); // push the to side to receive the new value (or not)
}
return null;
}
public void signal()
{
node.signal();
}
}

View File

@ -0,0 +1,50 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.ports;
import moe.nekojimi.nodeprocessor.links.Link;
import moe.nekojimi.nodeprocessor.nodes.Node;
/**
*
* @author jimj316
*/
public class OutPort<T> extends Port<T>
{
public OutPort(String name, Class<T> clazz, Node node)
{
super(name, clazz, node);
}
public void set(T value)
{
for (Link<T, ?, ?> link : links)
{
link.write(value);
}
}
public void pull()
{
node.signal();
}
@Override
public void connect(Link<T, ?, ?> link)
{
super.connect(link);
// also re-order the links into dependency order
links.sort((Link a, Link b) ->
{
Node aNode = a.getTo().getNode();
Node bNode = b.getTo().getNode();
return Node.DEPENDENCY_ORDER.compare(aNode, bNode);
});
}
}

View File

@ -0,0 +1,83 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package moe.nekojimi.nodeprocessor.ports;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import moe.nekojimi.nodeprocessor.links.Link;
import moe.nekojimi.nodeprocessor.nodes.Node;
/**
*
* @author jimj316
*/
public abstract class Port<T>
{
protected final String name;
protected final Class<T> clazz;
protected final Node node;
protected final List<Link<T, ?, ?>> links = new ArrayList<>();
public String getName()
{
return name;
}
public Class<T> getClazz()
{
return clazz;
}
public void connect(Link<T, ?, ?> link)
{
links.add(link);
}
public void disconnect(Link<T, ?, ?> link)
{
links.remove(link);
}
public Port(String name, Class<T> clazz, Node node)
{
this.name = name;
this.clazz = clazz;
this.node = node;
}
public Set<Node> getLinkedNodes()
{
Set<Node> ret = new HashSet<>();
for (Link<T, ?, ?> link : links)
{
Port otherSide;
if (link.getFrom() == this)
otherSide = link.getTo();
else
otherSide = link.getFrom();
ret.add(otherSide.node);
}
return ret;
}
public int getNumberOfConnections()
{
return links.size();
}
public Node getNode()
{
return node;
}
public List<Link<T, ?, ?>> getLinks()
{
return links;
}
}