Nick
unread,Jul 7, 2015, 1:23:26 PM7/7/15Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Sign in to report message
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to GPars Users
Hello,
Attempting to answer my own question here, having looked at the GPars
source...
Short answer: seems you have to supply and initialise your own
InternalActor field, and then you can control how and when it starts.
In a pinch it seems you can also patch the actor instance's methods,
despite it being a final class, insofar as they are called externally
(i.e. patching doStart() only works when called externally, not
internally, when the original method is still called).
Something like this. Which also shows my use case - I want to wrap an
SWT event loop, and only start actor messaging when it's ready. Note,
this is a condensed illustration, with parts omitted and not fully tested.
@ActiveObject
class GuiActor {
private final InternalActor internalActiveObjectActor; //
Presence prevents create() being called
private Display display; // for SWT
private Shell shell; // for SWT
private final CountDownLatch latch = new CountDownLatch(1);
public GuiActor() {
// We must perform the work that create() does here manually
internalActiveObjectActor = new InternalActor();
internalActiveObjectActor.setParallelGroup(Actors.defaultActorPGroup);
// At this point we can (hopefully safely) perform any
initialisation we need to do prior to messages arriving
// In our case, start a (blocking) SWT event loop in
another thread.
openSwtWindow()
internalActiveObjectActor.silentStart() // start acting
// at this point we can perform any initialisation we need
to do once message can be sent
}
private openSwtWindow() {
Thread t = new Thread({
// Run the gui
display = new Display();
try {
// ... set up display and shell here ...
latch.countDown();
// ... blocking event loop here ...
// Does not return until the user closes the window
}
catch(Exception e) {
// ... log error ...
}
finally{
// ... clean up ...
}
// Stop the relay actor to stop receiving any messages.
internalActiveObjectActor.stop()
});
t.start();
await(); // latch signal that display is ready
}
// waits for gui to start
private boolean await() {
try {
latch.await();
return true;
}
catch (InterruptedException e) {
e.printStackTrace(); // whatever
return false;
}
}
// Toy example of mutating the GUI
@ActiveMethod
public void setTitle(String msg) {
display.asyncExec {
shell.setText(msg);
}
}
}
Feedback as to whether I could be doing these things a better way welcomed.
Cheers!
N
This is what the @ActiveObject annotation appears to do, in more detail
than appears in the docs.
First, as the docs say, by dint of an AST transformation classes
annotated with @ActiveObject need a field called
"internalActiveObjectActor" of type InternalActor.
If it is present and the right class, it will be used as-is (as
exploited above).
If the field is present, but not an InternalActor, this is an error. So
don't do that.
Otherwise, the field gets declared and will be initialised using
InternalActor.create(Object groupId), passing a string naming the
parallel group. (This is the empty string by default implying the
default actor parallel group is used, but can be set to a custom group
name in the ActiveObjectRegistry, via the 'value' field of the
ActiveObject annotation.)
The InternalActor.create() method constructs an InternalActor instance,
sets its parallel group, and invokes its silentStart() method
immediately. This means the actor is started as soon as the object is
constructed.
As an aside, AST transform also clones the original methods of the
active object class but with private + final modifiers, and replaces
them with proxy methods that delegate to the internal actor's submit
method in order to call the originals, something like this:
// non-blocking; returns a promise
final private Object <methodname>(<params>) { return
internalActor.submit(this, <methodname>, <params>); }
// blocking; attempts to unpack the promise, or throw as appropriate
final private Object <methodname>(<params>) {
return internalActor.preProcessReturnValue(
internalActor.submitAndWait(this, <methodname>, <params>),
this,
<methodname>
);
}
To conclude: although my original idea was that the active object
constructor might try to set the internalActiveObjectActor's afterStart
method dynamically, this will never work, because:
a) silentStart() is used in the create() initialiser, which skips
sending the START_MESSAGE, so afterStart won't get invoked, and
b) even if start() was used, the internal actor field is initialised
inline, and so may have been started by the time the user-supplied
constructor runs.
...and the clincher
c) the InternalActor class is declared final, and the AST transform
includes a hardwired check for its fully-qualified class name.
So that's essentially ruled out all the loopholes for modifying the
InternalActor. The only option left is to pre-empt the AST transform
by create the internal actor field manually.