Why doesn't Penumbra use agents & watchers?

20 views
Skip to first unread message

drrobot

unread,
Nov 30, 2010, 7:14:44 AM11/30/10
to Penumbra
Penumbra seems great, and I am learning some interesting idioms from
looking at the code, but I have some questions about its architecture.
I freely admit I'm a novice at OpenGL and Clojure -- so with little
documentation for Penumbra at the moment I'm having slow going, but am
interested enough to keep trying. The examples help, but they often
don't clarify the design rationale.

As I read through the Penumbra source, I was wondering:

1. What technical reasons prompted the development in penumbra.app of
a thread control system (controller.clj), a queuing system
(queue.clj), and an event system (event.clj)? When I naively look at
these, it seems like these could be accomplished more idiomatically
using the queuing behavior of agents or events triggered from
watchers. Some sort of timing abstraction would still be necessary to
get the cool behavior of time-based queuing that Penumbra already
provides, of course.

2. Is it possible to load texture/model data in a background thread,
and through atom swaps start to use it in the OpenGL thread? I'm
looking for a dynamic way of load/unload objects into my simulation as
it continues to run.

3. Can multiple Penumbra apps use the same opengl context, data,
display lists, eetc?

As I understand it, OpenGL is essentially a single-threaded system, so
mapping that into the Clojure world would appear to suggest that we
use an agent. In fact, I am trying to figure out how to create
behavior like

;; Load the model, textures, whatever
(send opengl-agent load-model :robot1 "robot.clj")

;; Tell the agent to start drawing the model
;; by using some opengl-drawable protocol on the object
(send opengl-agent add-to-drawlist :robot1)

Pointers and advice would be most welcome.

Zach Tellman

unread,
Nov 30, 2010, 11:43:59 AM11/30/10
to penumb...@googlegroups.com
There needs to be an OpenGL context on any thread where we make OpenGL
calls. Multiple threads can share contexts, but this is extremely
difficult in practice. Rendering a texture while another thread is
updating its contents will cause the JVM to crash (not throw an
exception, mind you, just sigsegv and disappear), and I don't think
the cross-thread behavior is consistent across platforms.

Agents have their own thread pool, not a single thread, and we have no
easy way of registering an OpenGL context on any of them. Since we
care a lot about what thread events occur on, events.clj is how it is
for a reason. The queueing mechanism should probably just use
java.util.concurrent (I wasn't very familiar with it when I first
wrote queue.clj), but agents don't gain us much here either. Since we
can't use the agent's actual state for the reasons mentioned above,
we'd just be using agents as a thread pool, and a thread pool doesn't
give us the strict timing semantics we want.

There was originally a mechanism to do the sort of background loading
you mentioned, but since I couldn't figure out exactly what you could
and couldn't do on the background thread I took it out. However, if
you just want to create a sub-thread that shares the OpenGL context,
it's pretty easy:

(penumbra.app.core/with-app app
(penumbra.app/with-gl
*load robots and stuff*
(penumbra.app/update! *update state to have robots*)))

I can't guarantee this thread won't interfere with the main one, but
if you're creating new things and only then telling the main
application about them, I think you should be fine.

By the way, what obstacles have you encountered due to the lack of
documentation? I'd appreciate some guidance on what most needs to be
better documented.

Zach

drrobot

unread,
Dec 1, 2010, 7:50:03 AM12/1/10
to Penumbra
Thanks for the explanation. I can see how sharing the OpenGL context
with the whole agent threadpool would be a bad design, and how timing
constraints necessitate another solution. Thinking it over my original
idea again, I am left sheepishly wondering if there would even be any
benefit at all to implementing the agent interface for some fraction
of Penumbra's functionality. The callback-based model would suggest it
would be better to just present the (atomically changing) data to
Penumbra, and let the OpenGL callbacks handle the details of when and
in what context its processing occurs.

Still, it is somehow unsatisfying that the nice concurrency model of
Clojure is being limited defeated by OpenGL's single-threaded design.
I will play around with using multiple threads in the same context and
see what I learn. Maybe having a small threadpool of agents with the
opengl context might be useful for a limited set of background opengl
tasks?

As for documentation, I can only speak for the difficulties I
encountered myself -- they may or may not be typical.

1. Top priority should be to make sure all your functions have better
docstrings...and if possible, an sample use case, and where they
should probably be called. There are still several functions that lack
docstrings at present, and others that need to be more verbose.
2. I wasn't always sure what your examples were actually doing. Just
as an example: async.clj. Exactly what is happening asynchronously in
this example? Even a one-line description added to the namespace
metadata at the top of each file would help me understand if I should
spend time reading the source of the example or not.
3. I ran into the issue described in [https://github.com/ztellman/
penumbra/issues#issue/18] and had to change my xorg.conf, and was
quite puzzled for several hours. Why didn't app/display-mode! work?
Why does app/display-modes only return one mode?
4. Also on my documentation wish list for Christmas; a style guide for
using the most important functions of penumbra for my own evil
purposes: event/publish!, controller/wait!, app/enqueue!, error
handling, etc.
5. Not documentation-related, but I also want Penumbra in a JPanel!

Hope it's a useful data point for you, and thanks for writing
Penumbra,


Ivar

drrobot

unread,
Dec 1, 2010, 10:31:07 AM12/1/10
to Penumbra
Minor update: using with-app and with-gl, loading textures in another
thread and then calling swap! on the atom seems to work just fine.
That's actually pretty damn cool...it makes trying out a bunch of
different skyboxes really easy.

Now to see what can be done about those pesky polygons...


Ivar

Zach Tellman

unread,
Dec 1, 2010, 11:13:13 AM12/1/10
to penumb...@googlegroups.com
On Wed, Dec 1, 2010 at 4:50 AM, drrobot <drr...@gmail.com> wrote:
> Thanks for the explanation. I can see how sharing the OpenGL context
> with the whole agent threadpool would be a bad design, and how timing
> constraints necessitate another solution. Thinking it over my original
> idea again, I am left sheepishly wondering if there would even be any
> benefit at all to implementing the agent interface for some fraction
> of Penumbra's functionality. The callback-based model would suggest it
> would be better to just present the (atomically changing) data to
> Penumbra, and let the OpenGL callbacks handle the details of when and
> in what context its processing occurs.

Maybe I'm not understanding what you want, but app/update! or
app/enqueue! are kind of analogous to agents, in that they let you
trigger an update to the application state.

>
> Still, it is somehow unsatisfying that the nice concurrency model of
> Clojure is being limited defeated by OpenGL's single-threaded design.
> I will play around with using multiple threads in the same context and
> see what I learn. Maybe having a small threadpool of agents with the
> opengl context might be useful for a limited set of background opengl
> tasks?

Again, I haven't determined exactly what is and isn't allowed in these
secondary threads. A better approach, I think, is to just expose the
asynchronous loading of data, such that everything is invoked on the
main thread but doesn't block it.

>
> As for documentation, I can only speak for the difficulties I
> encountered myself -- they may or may not be typical.
>
> 1. Top priority should be to make sure all your functions have better
> docstrings...and if possible, an sample use case, and where they
> should probably be called. There are still several functions that lack
> docstrings at present, and others that need to be more verbose.
> 2. I wasn't always sure what your examples were actually doing. Just
> as an example: async.clj. Exactly what is happening asynchronously in
> this example? Even a one-line description added to the namespace
> metadata at the top of each file would help me understand if I should
> spend time reading the source of the example or not.
> 3. I ran into the issue described in [https://github.com/ztellman/
> penumbra/issues#issue/18] and had to change my xorg.conf, and was
> quite puzzled for several hours. Why didn't app/display-mode! work?
> Why does app/display-modes only return one mode?
> 4. Also on my documentation wish list for Christmas; a style guide for
> using the most important functions of penumbra for my own evil
> purposes: event/publish!, controller/wait!, app/enqueue!, error
> handling, etc.
> 5. Not documentation-related, but I also want Penumbra in a JPanel!

I'll do what I can about these. I've been extremely distracted by
work and other projects, and Penumbra's been kind of neglected. I'd
like to at least get things to a stable, well-documented state so that
I don't feel so bad about leaving it alone.

Zach

drrobot

unread,
Dec 2, 2010, 5:24:10 AM12/2/10
to Penumbra
Ciao Zach,

> Maybe I'm not understanding what you want, but app/update! or
> app/enqueue! are kind of analogous to agents, in that they let you
> trigger an update to the application state.

Yes, but isn't that update queued and single-threaded? Maybe I should
just explain what I am trying to do and you can tell me if I'm
thinking about this along the right lines.

I am working on a real-time robotics application targeting a machine
with 24 cores, so things must happen concurrently if at all possible.
Unlike simulators or video games, the model/texture data that OpenGL
renders will not come from some internal state owned by the OpenGL
thread, but is rather a snapshot of the observed sensor data. Using
app/enqueue! to modify the display seems like the wrong thing to do,
as I'm not sure about the rates at which data comes in from a handful
of asynchronous sensors, observers, controllers, etc. Rather, I would
prefer the OpenGL thread to simply dereference a bunch of object atoms
that describe the observed state of the robot, sensors, world, etc. A
collection of asynchronous threads may then modify the object atoms as
they need to, and only the objects know how to draw themselves (but
this means they need to be able to access OpenGL in a background
thread to prepare their display list or opengl drawing function).
Penumbra then just becomes a front-end for visualization, and does not
actually compute the state or trigger changes in state. Finally, I
would like to be able to build opengl representations of observed
objects dynamically as sensor data comes in, without disturbing the
60Hz opengl rendering loop.

Yesterday I tried concurrent loading of textures and model data using
futures, and both worked perfectly on my slow machine at home. To my
unpleasant surprise, this morning the texture loading on my faster
machine at work yields some visual glitches (although the model
loading still seems to work fine). If you have a moment, I would
appreciate it if you could take a look and tell me what is wrong, or
if the glitch also occurs on your machine.

Code and free non-commercial textures are here until I figure out how
to use github or gist or whatever.

http://clojure.roboloco.net/skybox.zip

Just fix the 'skybox-path' def at the bottom to match your path.


Ivar

drrobot

unread,
Dec 17, 2010, 10:41:49 PM12/17/10
to Penumbra
Ciao Zach,

It's been a couple weeks and no replies...Is this something you have
time to follow up on, or should I consider it something I should have
to solve myself after the Christmas break?


Ivar

Zach Tellman

unread,
Dec 18, 2010, 2:33:31 PM12/18/10
to penumb...@googlegroups.com
Sorry, I completely missed your previous message. Feel free to not
wait a full two weeks to get my attention next time.

Like I said before, using other threads in OpenGL is not a
deterministic experience. I've had wild variations in behavior across
computers, which is why I took out the easy way to use other threads.
There is an asynchronous way to load threads (i.e. enqueue the
loading, and poll to see when it's complete), but it's not currently
exposed.

My suggestion is that any time you touch the textures, use
(app/enqueue! ...). If this makes the rendering unacceptably choppy
we can look for an alternative, but that's the guaranteed safe
approach.

Zach

Reply all
Reply to author
Forward
0 new messages