David and I are putting the TPointer into the same TeaParty as the
TAvatar. The reason for this is that currently the TPointer is NOT
replicated, hence we are sending far more messages than we should if it
were. Once it is replicated, then the vast majority of its messages
will be sent locally, hence will be quite efficient. This in itself is
a good thing and will be part of both Mad Hatter and Jasmine.
In making this change of putting the TPointer into the same TParty as
the TAvatar, a problem I had ignored until now becomes a bit more
apparent. In Solar, all "selections" of objects occur locally. That is,
when I select a window, or for that matter generate a pointerEnter
message, this only occurs on the local version of the TWindow. Hence,
it is only highlighted locally. In some ways, this simplifies the idea
of selection and manipulation, as only the local guy is involved. Of
course, if any deep changes occur, then the rest of the world needs to
know about this, which is why we did the meta sends.
Of course, Mad Hatter and Jasmine don't work this way. Instead - every
message that is sent to a replicated object is itself replicated among
all of them. This means that when I have a pointerEnter event, this
message is sent to all of the target objects. For a TWindow to work
properly now, it must be aware that multiple users may be able to work
with it, and now IT must manage all of the users state - where before
it needed to manage only whether a single
pointerEnter/pointerOver/pointerLeave and a single
Why is this a problem? We now need to redesign the window (and
virtually all of the other TObjects) to either service all of the
events on a first come/first serve lock-out everyone else until
released, or we need to keep track of all of users currently
interacting with the object. For example, in Solar, if I select a
windows drag area on one machine and you select it on another, this
works nicely because we are just sending updated location information
to each of the windows. We have a nice tug-of-war demonstrating
robustness. With the new model, the only way I can see this working in
this case is whoever gets to the window first controls it completely.
Some events can still be handled properly, like key presses, but most
act over time, like dragging, drawing a line, etc. In the case of the
TWindow, to drag, it keeps track of the camera's normal vector so that
it can drag perpendicular to this. Multiple cameras from different
locations and orientations would make this fail, possibly dramatically.
So what to do?
Some objects would require a lock-out to work properly, usually between
a pointerDown and pointerUp. The pointerEnter and pointerLeave should
NOT require a lock-out because these usually not deep modifications,
but they would require some kind of reference count. That is, we need
to track how many pointerEnter's have occurred and match these with the
pointerLeaves. In the case of the window, we hilite on the first
pointerEnter, and unhilite on the last pointerLeave. No matter what,
this is more complex code.
The lockout may not be that big of a problem as long as everyone knows
that that is the situation. For ICE, what I did was when a lockout
occurred, I hilited the object in red to show that you could not touch
it. This is actually something that can be done locally with something
like the following:
downPointer = ogl camera pointer ifTrue:[
self hilite: go color.]
self hilite: stop color].
Not pretty, but it was quite effective in ICE from a UI point of view.
A very clear indication of what the state of the object is, which
avoids confusion. The fact is, my perspective on the world IS different
from yours. What I can and can't do IS different from what you can and
can't, and this MUST be made explicit.
The pointerEnter/pointerLeave reference count and the
pointerDown/pointerUp lockout is a pretty simple pattern to implement.
If anyone has a better idea, feel free to let me know.