Orc sites imported with the site declaration are implemented as
Java classes which extend orc.runtime.sites.Site.
Here we will summarize the most important features of this and related classes;
for a detailed description, refer to the Javadoc.
Sites must implement a single method: callSite(Args args,
Token token). The Orc engine calls this method whenever the site is
called by the Orc program.
The args argument (of type Args) is used to get the arguments which were passed to
the site call by the Orc program. It has the following important methods:
getArg(int
n)
Object. For example, to get the value of the
first argument, call args.getArg(0).intArg(int n), stringArg(int n), boolArg(int
n), etc.ArgumentTypeMismatchException.
The token argument (of type Token) is a sort of callback which is used by the site
to return a value or signal that it has halted. It has the following important
methods:
resume(Object
value)
value from the site call. For example, to return the
number 5, call token.resume(5). To publish a
signal without returning any specific value, call token.resume().error(TokenException
problem)
callSite method; the engine will call
token.error on its behalf. To convert a Java
exception to the necessary TokenException type,
wrap it in an instance of orc.error.runtime.JavaException.die()
if uses this method to halt when called with a
false argument.
The callSite method must return control to its
caller within a short, deterministic amount of time. To implement a site which
blocks or waits for some condition, callSite must
save the token and return without calling any of
the above methods. Then when it is time for the site call to return, call the
appropriate method on the token.
Many sites are deterministic, always returning a value immediately when called.
Such sites can be implemented easily by extending orc.runtime.sites.EvalSite and implementing the method
evaluate(Args args). This method should read the
arguments from args as usual, and then return the
value to be returned from the site call, or throw a checked exception to
indicate an error.
Some sites need to perform blocking IO or use other Java blocking methods like
Object#wait(). This cannot be done directly in
the callSite or EvalSite#evaluate methods because that might block the
Orc engine. Instead, extend orc.runtime.sites.ThreadedSite and implement the method
evaluate(Args args). This method should read the
arguments from args as usual, and then return the
value to be returned from the site call, or throw a checked exception to
indicate an error. Your evaluate method will be
automatically run asynchronously in a new thread so that it does not interfere
with the Orc engine.
Beware that the Orc engine may be configured to only allow a fixed number of site threads. If too many sites need to use threads simultaneously, some will block until threads become available.
Recall that the notation x.msg corresponds to calling the site
x with the special message value msg. To implement a
site which responds to such messages, extend orc.runtime.sites.DotSite.
In your site's constructor, call the method addMember(String message, Object value) once for each
message you wish the site to respond to. The call addMember(m, v) instructs the site to return the value
v when it is called with the message m.
Any object can be used as a member value, even another site. Anonymous inner
classes extending Site are often used as member
values which behave like methods of the enclosing DotSite.
We will implement a simplified version of Orc's Buffer site,
called ExampleBuffer. The goal will be to be able
to run this Orc program:
-- Import the site definition site ExampleBuffer = ExampleBuffer -- Create a new buffer site val b = ExampleBuffer() -- wait for a value in the buffer b.get() -- put a value in the buffer | b.put(3) >> stop -- Publishes: 3
The first step is to create ExampleBuffer.java. The ExampleBuffer site is actually a discovery site: all
it does when called is create and return a reference to a new site (the buffer
instance).
public class ExampleBuffer extends EvalSite { public Object evaluate(Args args) { return new ExampleBufferInstance(); } }
Next we must implement ExampleBufferInstance,
which defines the behavior of an individual buffer. We can put this class
either in its own file or in ExampleBuffer.java, since
that is the only class which refers to it directly. How does an instance
behave? If we write b.get(), that means that b
responds to the message get with a site which we call to get a
value from the buffer. So we'll extend DotSite,
telling it to respond to the message "get" with a GetMethod site, and likewise for "put". We'll use a
linked list to store the actual contents of the buffer.
class ExampleBufferInstance extends DotSite { private LinkedList<Object> contents = new LinkedList<Object>(); public ExampleBufferInstance() { addMember("get", new GetMethod()); addMember("put", new PutMethod()); } }
The GetMethod site is implemented as an inner
class so it has easy access to the contents of the buffer. In the
implementation we first check if there is an item in the buffer. If so, we
return it. Otherwise, we must store the token in a queue to be notified when
the next item is put in the buffer. We synchronize on the outer object to
protect against concurrent modification.
private LinkedList<Token> waiters = new LinkedList<Token>(); private class GetMethod extends Site { public void callSite(Args args, Token token) { synchronized (ExampleBufferInstance.this) { if (!contents.isEmpty()) { token.resume(contents.removeFirst()); } else { waiters.add(token); } } } }
PutMethod is even simpler since it doesn't need to
block. If there is a token waiting for an item, we give it the item. Otherwise
we put the item in the buffer. We synchronize on the outer object to protect
against concurrent modification. When done we return a dummy "signal" value.
private class PutMethod extends EvalSite { public Object evaluate(Args args) { Object item = args.getArg(0); synchronized (ExampleBufferInstance.this) { if (!waiters.isEmpty()) { waiters.removeFirst().resume(item); } else { contents.add(item); } } return signal(); } }
Putting it all together, here is the entire implementation:
import java.util.LinkedList; import orc.runtime.sites.EvalSite; import orc.runtime.sites.DotSite; import orc.runtime.sites.Site; import orc.runtime.Token; public class ExampleBuffer extends EvalSite { public Object evaluate(Args args) { return new ExampleBufferInstance(); } } class ExampleBufferInstance extends DotSite { private LinkedList<Object> contents = new LinkedList<Object>(); private LinkedList<Token> waiters = new LinkedList<Token>(); private class GetMethod extends Site { public void callSite(Args args, Token token) { synchronized (ExampleBufferInstance.this) { if (!contents.isEmpty()) { token.resume(contents.removeFirst()); } else { waiters.add(token); } } } } private class PutMethod extends EvalSite { public Object evaluate(Args args) { Object item = args.getArg(0); synchronized (ExampleBufferInstance.this) { if (!waiters.isEmpty()) { waiters.removeFirst().resume(item); } else { contents.add(item); } } return signal(); } } public ExampleBufferInstance() { addMember("get", new GetMethod()); addMember("put", new PutMethod()); } }