Recording in Croquet

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Recording in Croquet

Liz Wendland
Hi,

At the University of Minnesota we are interested in recording all the
activities that occur on a given Croquet island for later playback.  We
want this functionality because:

1) Instructors won't always be present in a space, but still want to be
able to review the activities that occurred.
2) Sometimes Croquet interactions might take place between two student's
computers and they might want to "hand in" what they did to their
instructor.
3) Classes or seminars given in Croquet could be reviewed later by others.

I don't think this is functionality only of interest to educators... I
can imagine Qwaq Forums wanting to record a meeting for later playback.

Anyway, we have hit a brick wall with this and are hoping that some
people would also be interested enough in recording/playback that they
would like to start a dialog about this.  I'll briefly describe our
approach:

We capture all messages at the TSimpleRouter>>send:from: level and
flatten them to a file.  We then read these back in later, reconstitute
and send them just as if they were coming from TSimpleRouter.  This
works absolutely fantastically for most things.  The problem we have
encountered is if anything new is introduced to the island while it is
being recorded.  During playback, when the object is added to the space
(using the addChild: method) it will fail trying to decode the
arguments.  We have thought that perhaps the OID's of things were not
getting set correctly, but I am not 100% sure of that and am even
doubting it because it still happens if we block all other events on the
playback island.

Andreas mentioned that we could perhaps capture at a higher level than
the router.  The thing that calls the router seems to be the
TFarRef>>future call and that seems to be as high as we can capture
unless we want to add code to every object on the island.

I admit we've been staring at this for a long time now and there
probably is something obvious that we can no longer see... but we would
love to work or talk with anyone else interested in this.  I can provide
more detail and code if that is helpful.

Thanks!
Liz Wendland
University of Minnesota
Reply | Threaded
Open this post in threaded view
|

Re: Recording in Croquet

David P. Reed
Liz - there is a semantic question here regarding what you want to
happen with respect to new object creation.

Recording the message flow in a computation works correctly to
reconstruct the computation exactly, given a particular starting
state.   You must ensure that the particular starting state is indeed
the same, otherwise, you end up with a huge mess.

Understanding why the mess happens is useful to figuring out a good
way.  Rather than "higher level" (what is "level" anyway?  That is an
arbitrary distinction that does not appear in the code, so it leads to
programming by magic belief... :-( ) the key is to think about groups of
objects.  (once upon a time, this concept of groups was referred to by
me as TeaParties.  Don't confuse with Islands.  That's related but
merely an implementation hack).

Essentially, you can view any collection of objects you can draw a line
around as a source and sink of messages.   Since only messages
communicate information, the messages *into* the collection completely
determine the messages *out*.   The messages prior to a particular
TeaTime (back to the origin) *are* the state of the collection at that
TeaTime - this is what it means to be deterministic.

In a few words, then, this is the entire semantics of TeaTime and
TeaObjects and collections of TeaObjects.

If you want to "record" for later replay, you probably want to record
only a subset of the objects, and only for a particular period of
TeaTime.   And presumably, you want to be able to play the recording
back with new messages (such messages as input events from the replayer,
state requests from the replayer desiring to render from a different
point of view, etc.).   Otherwise, there is no value to a recording.  At
the same time, you want to be able to discard messages that are sent out
in the original execution of the collection, so they don't cause havoc
to collections of objects not interested in that replay.

Note that the "replay" cannot be identical.   But one hopes that its
behavior is not affected much during the replay.   You wouldn't want it
to be completely identical - you want effective identity.

So here are the semantic problems you need to deal with somehow.  
Dealing with means defining what you mean.  Then the implementation
should fall out nicely and directly.

First, objects outside of your collection may be part of causal chains
that link outgoing messages back to incoming messages into the
collection.   In other words, an event inside your collection may have
an effect on future events in your collection because a message goes out
from the collection to outside objects, then comes back in.    If you
think about this, recording all incoming messages into the collection
solves this problem - as long as you don't introduce new messages that
can cause new outgoing messages that weren't there the first time the
simulation was run which then result in causal chains generating
messages back in.

One solution to this first problem is to be careful to select the
"right" set of objects to be in your collection.   The rule needs to
make sure that any of the newly added messages cannot add
causally-linked flows involving objects outside the collection.  This
rule is too strict, because there can be objects outside the collection
(such as the ones used in replaying itself - the user's rendering
environment and input management) that are correct.

But this doesn't completely solve the problem, because some of the input
messages into the collection will be messages that result from the
causal chains arising from output messages.  Those messages should not
be included in the recording.  One way to squeeze them out, perhaps, is
to recognize that causally linked messages have the same TeaTime as the
ones that cause them.   Thus, incoming messages that have the same
TeaTime as a prior (in execution time) outgoing message are special
cases.   They *may* be part of a causal chain.  At replay time, playing
them back can be problematic because they will be essentially
duplicates, if the environment handles the outgoing message the same way
as the first time.  (of course the environment is *different* during the
playback, so the result won't be exactly a duplicate - but the old
causally linked message should not be played back).

Second, the state as defined above is the sequence of messages from the
beginning of time till the recording starts.   That is different from a
collection of object ids.   Replaying those messages will create a new
set of objects, distinct from the original set (but completely
isomorphic to it - it is a replicated computation of the first one).

Here you have to decide what you want.   If you want a new set of
objects, then you have to be careful what *state* you start with.  
Don't reuse the old objects.   The "image" you might save is just a
compressed form of the past history of input messages.  If you an
isomorphic set of object states with new object ids, and translate all
messages to use those new object ids instead of those in the collection,
that works in the sense of recreating history.

If you want to play back the messages against the original set of
objects (in their later-in-time states) then you can do so, but the set
of objects includes objects that have been deleted.  Not clear what that
means.  And more complicatedly, the causal-chain loopback messages (for
want of a better name) that you recorded probably should NOT be played
back if there is an environment that is doing the same thing.

Note that I have not "solved" the problem, but attempted to lay out the
semantic issues you are struggling with.

It's pretty clear that simple recording of low level events won't work.

Recording input device events only might work - but recognize that they
are interpreted relative to user's UI state, which is not available at
playback time (as an example, the input event clicking on a 2D point is
interpreted by projecting a ray into the 3D space, and asking the
objects touched what to do with the event - this assumes a particular 3D
camera view).





Liz Wendland wrote:

> Hi,
>
> At the University of Minnesota we are interested in recording all the
> activities that occur on a given Croquet island for later playback.  
> We want this functionality because:
>
> 1) Instructors won't always be present in a space, but still want to
> be able to review the activities that occurred.
> 2) Sometimes Croquet interactions might take place between two
> student's computers and they might want to "hand in" what they did to
> their instructor.
> 3) Classes or seminars given in Croquet could be reviewed later by
> others.
>
> I don't think this is functionality only of interest to educators... I
> can imagine Qwaq Forums wanting to record a meeting for later playback.
>
> Anyway, we have hit a brick wall with this and are hoping that some
> people would also be interested enough in recording/playback that they
> would like to start a dialog about this.  I'll briefly describe our
> approach:
>
> We capture all messages at the TSimpleRouter>>send:from: level and
> flatten them to a file.  We then read these back in later,
> reconstitute and send them just as if they were coming from
> TSimpleRouter.  This works absolutely fantastically for most things.  
> The problem we have encountered is if anything new is introduced to
> the island while it is being recorded.  During playback, when the
> object is added to the space (using the addChild: method) it will fail
> trying to decode the arguments.  We have thought that perhaps the
> OID's of things were not getting set correctly, but I am not 100% sure
> of that and am even doubting it because it still happens if we block
> all other events on the playback island.
>
> Andreas mentioned that we could perhaps capture at a higher level than
> the router.  The thing that calls the router seems to be the
> TFarRef>>future call and that seems to be as high as we can capture
> unless we want to add code to every object on the island.
>
> I admit we've been staring at this for a long time now and there
> probably is something obvious that we can no longer see... but we
> would love to work or talk with anyone else interested in this.  I can
> provide more detail and code if that is helpful.
>
> Thanks!
> Liz Wendland
> University of Minnesota
>
Reply | Threaded
Open this post in threaded view
|

Re: Recording in Croquet

Andreas.Raab
In reply to this post by Liz Wendland
[Resend; I first sent this from an account that wasn't subscribed]

Hi Liz -

I think David gave a good overview of the associated issues so I'll
stick to the issues at hand.

I'm pretty sure that the problem you see with new objects being created
is related to a problem with OIDs. This is the only explanation and it's
quite likely depending on how exactly you "feed" the events into the
stream. Basically, the replay must be completely passive; you cannot
send any other messages into the island because they will change the
state that later messages expect to find.

Object names are just the most probable place for these issues to occur
because the naming system uses a pseudo-RNG which diverges almost
instantly if "additional" messages have been sent in the meantime. The
issue with object names could be fixed though; I'm just not sure if it's
worth it ("worth it" as in: whether the problems introduced by extra
events just get a lot harder to find and more obscure).

Depending on what exactly you are trying to do, a perhaps better way to
deal with these replays may be to think of them as one of the little 3D
portals (miniature worlds) and all you can do is watch what is going on
in there but not yourself interact with it. In which case (because you
can't interact with the embedded space) there shouldn't be any problem
at all.

If that (e.g., replaying of messages without any additional events)
doesn't work then there is something deeper broken.

Cheers,
   - Andreas


Liz Wendland wrote:

> Hi,
>
> At the University of Minnesota we are interested in recording all the
> activities that occur on a given Croquet island for later playback.  We
> want this functionality because:
>
> 1) Instructors won't always be present in a space, but still want to be
> able to review the activities that occurred.
> 2) Sometimes Croquet interactions might take place between two student's
> computers and they might want to "hand in" what they did to their
> instructor.
> 3) Classes or seminars given in Croquet could be reviewed later by others.
>
> I don't think this is functionality only of interest to educators... I
> can imagine Qwaq Forums wanting to record a meeting for later playback.
>
> Anyway, we have hit a brick wall with this and are hoping that some
> people would also be interested enough in recording/playback that they
> would like to start a dialog about this.  I'll briefly describe our
> approach:
>
> We capture all messages at the TSimpleRouter>>send:from: level and
> flatten them to a file.  We then read these back in later, reconstitute
> and send them just as if they were coming from TSimpleRouter.  This
> works absolutely fantastically for most things.  The problem we have
> encountered is if anything new is introduced to the island while it is
> being recorded.  During playback, when the object is added to the space
> (using the addChild: method) it will fail trying to decode the
> arguments.  We have thought that perhaps the OID's of things were not
> getting set correctly, but I am not 100% sure of that and am even
> doubting it because it still happens if we block all other events on the
> playback island.
>
> Andreas mentioned that we could perhaps capture at a higher level than
> the router.  The thing that calls the router seems to be the
> TFarRef>>future call and that seems to be as high as we can capture
> unless we want to add code to every object on the island.
>
> I admit we've been staring at this for a long time now and there
> probably is something obvious that we can no longer see... but we would
> love to work or talk with anyone else interested in this.  I can provide
> more detail and code if that is helpful.
>
> Thanks!
> Liz Wendland
> University of Minnesota
>
Reply | Threaded
Open this post in threaded view
|

Re: Recording in Croquet

Liz Wendland
Hi All,

Thanks so much to David and Andreas for their excellent responses.  
Everything they said made sense and I don't think I am disobeying any of
the basic axioms described.  I decided the easiest thing for me to do to
explain what I am seeing was to create a simple package demonstrating
it.  I have attached it to this posting.

Basically it is a new morph called RecordingTest.  It has two items in
the zoom menu "Record" and "Play".  To see my problem, drag out the
morph, bring up the Zoom Navigation and click on Record.  Quit the
morph.  Two files are created:
cache/liz.c3d  - the saved island
Recordings/liz.caa  - a recording of a TRectangle being added to the space.
If you then drag out RecordingTest again and click Play you will get an
error "No such object".

The approach we have taken is that we capture certain events at the
Router>>send:from: level.  On playback we then emulate the Router and
dispatch the message to the proper receiver.  The problem occurs on the
addChild: call.  The arguments for that message are encoded, but when we
attempt to decode that message it can't find the ID on the island.  I
have turned on logging in the TObjectID>>from: method and I can see that
my TRectangle has an identical ID in both the record and playback
phase.  Why can't the decode find it?

Please note that on playback I am restoring the saved island as a new
island and the only thing that happens on the island is the playback of
the saved events.  I store a TPlaybackCamera on the island before I save
it so I don't have to create any events there on playback.

Thanks again for any ideas!
Liz

Andreas Raab wrote:

> [Resend; I first sent this from an account that wasn't subscribed]
>
> Hi Liz -
>
> I think David gave a good overview of the associated issues so I'll
> stick to the issues at hand.
>
> I'm pretty sure that the problem you see with new objects being created
> is related to a problem with OIDs. This is the only explanation and it's
> quite likely depending on how exactly you "feed" the events into the
> stream. Basically, the replay must be completely passive; you cannot
> send any other messages into the island because they will change the
> state that later messages expect to find.
>
> Object names are just the most probable place for these issues to occur
> because the naming system uses a pseudo-RNG which diverges almost
> instantly if "additional" messages have been sent in the meantime. The
> issue with object names could be fixed though; I'm just not sure if it's
> worth it ("worth it" as in: whether the problems introduced by extra
> events just get a lot harder to find and more obscure).
>
> Depending on what exactly you are trying to do, a perhaps better way to
> deal with these replays may be to think of them as one of the little 3D
> portals (miniature worlds) and all you can do is watch what is going on
> in there but not yourself interact with it. In which case (because you
> can't interact with the embedded space) there shouldn't be any problem
> at all.
>
> If that (e.g., replaying of messages without any additional events)
> doesn't work then there is something deeper broken.
>
> Cheers,
>   - Andreas
>
>
> Liz Wendland wrote:
>> Hi,
>>
>> At the University of Minnesota we are interested in recording all the
>> activities that occur on a given Croquet island for later playback.  
>> We want this functionality because:
>>
>> 1) Instructors won't always be present in a space, but still want to
>> be able to review the activities that occurred.
>> 2) Sometimes Croquet interactions might take place between two
>> student's computers and they might want to "hand in" what they did to
>> their instructor.
>> 3) Classes or seminars given in Croquet could be reviewed later by
>> others.
>>
>> I don't think this is functionality only of interest to educators...
>> I can imagine Qwaq Forums wanting to record a meeting for later
>> playback.
>>
>> Anyway, we have hit a brick wall with this and are hoping that some
>> people would also be interested enough in recording/playback that
>> they would like to start a dialog about this.  I'll briefly describe
>> our approach:
>>
>> We capture all messages at the TSimpleRouter>>send:from: level and
>> flatten them to a file.  We then read these back in later,
>> reconstitute and send them just as if they were coming from
>> TSimpleRouter.  This works absolutely fantastically for most things.  
>> The problem we have encountered is if anything new is introduced to
>> the island while it is being recorded.  During playback, when the
>> object is added to the space (using the addChild: method) it will
>> fail trying to decode the arguments.  We have thought that perhaps
>> the OID's of things were not getting set correctly, but I am not 100%
>> sure of that and am even doubting it because it still happens if we
>> block all other events on the playback island.
>>
>> Andreas mentioned that we could perhaps capture at a higher level
>> than the router.  The thing that calls the router seems to be the
>> TFarRef>>future call and that seems to be as high as we can capture
>> unless we want to add code to every object on the island.
>>
>> I admit we've been staring at this for a long time now and there
>> probably is something obvious that we can no longer see... but we
>> would love to work or talk with anyone else interested in this.  I
>> can provide more detail and code if that is helpful.
>>
>> Thanks!
>> Liz Wendland
>> University of Minnesota
>>


Recording-etw.3.mcz (12K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Recording in Croquet

Andreas.Raab
Hi Liz -

Liz Wendland wrote:
> Everything they said made sense and I don't think I am disobeying any of
> the basic axioms described.  I decided the easiest thing for me to do to
> explain what I am seeing was to create a simple package demonstrating
> it.  I have attached it to this posting.

Thanks, this is very helpful. The problem you are seeing is caused by
promise-pipelining, e.g., the ability to send messages to objects that
don't even exist yet. In your example you do something like here:

        b := self activeIsland future new: TRectangle.
        b wait.
        pause := self activeSpace future addChild: b.
        pause wait.

Which is at the heart of the problem. First, there is really no need to
add the waits there - promise pipelining ensures that you can simply write:

        b := self activeIsland future new: TRectangle.
        pause := self activeSpace future addChild: b.

This is also the cause of the problem you are seeing. When a client
creates a promise for a future send, it assigns a name to it which
describes "the object resulting from executing some message" and which
is consequently independent of the name of the object it ultimately
results to. It is *that* name which is being looked for when you see the
error (print out the name of the variable "b" above to see it) and it is
that name that isn't being found (not the object's internal name).

This is why in TIsland>>execute: the result of the computation is stored
under the ID associated with that message (which is the name of the
promise that was created when it was send).

Obviously, your code needs to do the same, e.g., register the result of
the computation under the id of the message being executed. Something
like here:

TPlaybackCamera>>performMessage: msg
   "... yaddaya ..."
   result := receiver  perform: msg selector withArguments:  arguments.
   msg id ifNotNil:[
     Processor activeIsland register: result name: msg id.
   ].

Cheers,
   - Andreas
Reply | Threaded
Open this post in threaded view
|

Re: Recording in Croquet

Liz Wendland
Dear Andreas,

Thanks so much for the excellent advice!  Your fix works like a charm
for my example. We are finally able to move ahead with our recording
code.  :)  I need to polish it a bit and then I'll post it to
Contributions...

Thanks again!
Liz Wendland


Andreas Raab wrote:

> Hi Liz -
>
> Liz Wendland wrote:
>> Everything they said made sense and I don't think I am disobeying any
>> of the basic axioms described.  I decided the easiest thing for me to
>> do to explain what I am seeing was to create a simple package
>> demonstrating it.  I have attached it to this posting.
>
> Thanks, this is very helpful. The problem you are seeing is caused by
> promise-pipelining, e.g., the ability to send messages to objects that
> don't even exist yet. In your example you do something like here:
>
>     b := self activeIsland future new: TRectangle.
>     b wait.
>     pause := self activeSpace future addChild: b.
>     pause wait.
>
> Which is at the heart of the problem. First, there is really no need
> to add the waits there - promise pipelining ensures that you can
> simply write:
>
>     b := self activeIsland future new: TRectangle.
>     pause := self activeSpace future addChild: b.
>
> This is also the cause of the problem you are seeing. When a client
> creates a promise for a future send, it assigns a name to it which
> describes "the object resulting from executing some message" and which
> is consequently independent of the name of the object it ultimately
> results to. It is *that* name which is being looked for when you see
> the error (print out the name of the variable "b" above to see it) and
> it is that name that isn't being found (not the object's internal name).
>
> This is why in TIsland>>execute: the result of the computation is
> stored under the ID associated with that message (which is the name of
> the promise that was created when it was send).
>
> Obviously, your code needs to do the same, e.g., register the result
> of the computation under the id of the message being executed.
> Something like here:
>
> TPlaybackCamera>>performMessage: msg
>   "... yaddaya ..."
>   result := receiver  perform: msg selector withArguments:  arguments.
>   msg id ifNotNil:[
>     Processor activeIsland register: result name: msg id.
>   ].
>
> Cheers,
>   - Andreas