Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Steve Bennett
On Sat, Feb 26, 2011 at 2:52 AM, Dave Stubbs  wrote:
> Yeah, what we have is a nested and OO implementation of a simple stack
> with markers. This allows you to do cool stuff like encapsulate
> complex actions without bothering the user with extra undo steps or
> adding lots of boiler plate code around the "RecordAction" stage.

Don't get me wrong, I think the implementation is fantastic. The only
thing I'm commenting on is some of the unintuitive naming and
mechanics.

For example, passing a function to an operation, where the function
represents a storage operation for an undoable action...is not very
intuitive. In practice, exactly one of two things gets passed to those
functions: either MainUndoStack.getInstance().push, or some
CompositeUndoableAction.addAction. It would therefore be possible to
replace declarations like this:

public function createWay(tags:Object, nodes:Array,
performCreate:Function):Way {
var way:Way = new Way(nextNegative, 0, tags, true, nodes.concat());
performCreate(new CreateEntityAction(way, setWay));
...

with

public function createWay(tags:Object, nodes:Array, actionStack:
CompositeUndoableAction=null):Way {
var way:Way = new Way(nextNegative, 0, tags, true, nodes.concat());
recordAction(actionStack, new CreateEntityAction(way, setWay));
...

where recordAction is:

protected function recordAction(stack: CompositeUndoAbleAction,
action: Action):void {
  if (stack)
stack.push(action)
  else
MainUndoStack.getInstance().push(action);
}

Personally, I think passing either a composite action stack, or null,
to an operation is a lot more intuitive than passing a function. And
it would make some simpler:

w = createWay({}, nodes);

Now, the fact that it's creating an action which is being placed on
the main undo stack is totally invisible. Magic behind the scenes, and
all that.

Steve

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev


Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Steve Bennett
On Sat, Feb 26, 2011 at 12:43 AM, Andy Allan  wrote:
> This is where I could be wrong, but I think that it's critical to the
> redo part. When undo'ing an action the action is added to the redo
> list, so if adding an action has side-effects that'll blow up during
> redoing the action. Also, if the action works based on side effects
> that aren't tracked within the action itself, then it won't work
> properly when being redone.

Ok, I obviously wasn't explaining myself well, so I'll let some code
do the talking:

http://trac.openstreetmap.org/changeset/25431

This solves my problem: actions are carried out immediately, and then
added to the MainUndoStack later. It's not that the actions had "side
effects", I just needed access to the "main effect" immediately. The
difference is from this:

Create action A
Create action B
Create action C

Add to main undo stack
- do action A
- do action B
- do action C

To this:

Create action A
- do action A
Create action B
- do action B
Create action C
- do cation C

Add to main undo stack
- (already done).

I might be overlooking something, of course. I still get a couple of
spurious entries in the undo list which I haven't really investigated
yet. And there were some weird fake duplicate nodes being shown, which
might be a symptom of node state not being managed properly.

Steve

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev


Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Steve Bennett
On Sat, Feb 26, 2011 at 12:47 AM, Andy Allan  wrote:
> Without fully understanding things - do you? Instead of splitting the

Well, I'm sure that it's theoretically possible to write virtually any
code in such a way that I don't need the intermediate state. But it
might be less readable. And it's harder. And probably the Actions need
to be clearer about their postconditions. For example, it should be
possible to know for certain where the new way generated by
SplitWayAction will figure in the node's parentWays. (It's probably
deterministic, but it's not documented.)

Without having access to intermediate state, code is probably also
going to be more fragile, because you can't do sanity checks.

> way and deleting one half[1] are you not instead figuring out which
> nodes to delete from the way?

If you look at my commit, I actually wrote two completely different
versions - because I couldn't figure out for the life of me why the
first one (the one that's not commented out) didn't work.

> Given that you know before you start
> which end of the ways you'll delete from, then it's probably
> possible[2] to do it that way.

That works for the "delete all nodes between the junction and the
centre of the roundabout" approach. It doesn't work for the "delete
the way that is between the junction and the centre of the roundabout"
approach, because I don't know which way that is without looking
through junction.parentWays. (Could be missing something obvious
here.)

Steve

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev


Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Dave Stubbs
On Fri, Feb 25, 2011 at 1:43 PM, Andy Allan  wrote:
> On Fri, Feb 25, 2011 at 1:17 PM, Steve Bennett  wrote:
>
>> Does this absolutely have to be the case? I can't quite understand,
>> from a theoretical point of view, why this principle is necessary. Why
>> not add the action to a stack, and also carry it out now? What's the
>> benefit of maintaining the current state as long as possible, then
>> doing all the actions in one flurry?
>
> This is where I could be wrong, but I think that it's critical to the
> redo part. When undo'ing an action the action is added to the redo
> list, so if adding an action has side-effects that'll blow up during
> redoing the action. Also, if the action works based on side effects
> that aren't tracked within the action itself, then it won't work
> properly when being redone. Hence everything needs to be done by
> adding things to an action, and it needs to be completely
> self-contained so it can be moved back and forward between the
> different stacks.
>
> But I might be wrong.


You're not. That's exactly why you need to do it that way -- if you're
doing a simple composite undoable action anyway.

It may get a little confusing, but you can of course create your own
UndoableAction class. In that case during the doAction method you're
free to make any direct changes you like, calling other action's
doAction methods to do so. The only catch is that you have to be able
to put things back again how you found them (probably by calling
undoAction in the right order). And then properly handle a redo. The
easiest method of doing that is override CompositeUndoableAction,
pushing actions as you do them, allowing the undo be handled normally,
except that it'd have to clear the action list when done. It generally
cleaner and safer to use the existing actions and compose.

If you don't understand what I just said then I suggest sticking to
the existing actions as there's less scope for breaking things!


>
>> And just to add a bit more incoherent rambling, would a simpler model
>> be, instead of passing around these stacks everywhere, to have just a
>> main undo stack with two functions:
>> 1) RecordAction which adds an action to the undo stack, and carries it out.
>> 2) FinishUndoBlock, which groups all actions since the last time it
>> was called, into one block, which will all be undone with a single
>> keystroke. Sort of the equivalent of closing a changeset.
>
> That's just an unclear representation of a composite action! The
> RecordAction is undo.push, the FinishUndoBlock is
> MainUndoStackaddAction(), and the
> carrying-out-before-we're-finished is the bad idea that we need to
> avoid.
>

Yeah, what we have is a nested and OO implementation of a simple stack
with markers. This allows you to do cool stuff like encapsulate
complex actions without bothering the user with extra undo steps or
adding lots of boiler plate code around the "RecordAction" stage.

Dave

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev


Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Andy Allan
On Fri, Feb 25, 2011 at 1:17 PM, Steve Bennett  wrote:
>  I
> actually needed the split to have taken place so I could identify
> which half of the split was the one that I wanted to delete.

Without fully understanding things - do you? Instead of splitting the
way and deleting one half[1] are you not instead figuring out which
nodes to delete from the way? Given that you know before you start
which end of the ways you'll delete from, then it's probably
possible[2] to do it that way.

Cheers,
Andy

[1] My split-way-and-get-relations-right code is sobbing at the
injustice of it all
[2] This is not a money-back guarantee offer

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev


Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Andy Allan
On Fri, Feb 25, 2011 at 1:17 PM, Steve Bennett  wrote:

> Does this absolutely have to be the case? I can't quite understand,
> from a theoretical point of view, why this principle is necessary. Why
> not add the action to a stack, and also carry it out now? What's the
> benefit of maintaining the current state as long as possible, then
> doing all the actions in one flurry?

This is where I could be wrong, but I think that it's critical to the
redo part. When undo'ing an action the action is added to the redo
list, so if adding an action has side-effects that'll blow up during
redoing the action. Also, if the action works based on side effects
that aren't tracked within the action itself, then it won't work
properly when being redone. Hence everything needs to be done by
adding things to an action, and it needs to be completely
self-contained so it can be moved back and forward between the
different stacks.

But I might be wrong.

> And just to add a bit more incoherent rambling, would a simpler model
> be, instead of passing around these stacks everywhere, to have just a
> main undo stack with two functions:
> 1) RecordAction which adds an action to the undo stack, and carries it out.
> 2) FinishUndoBlock, which groups all actions since the last time it
> was called, into one block, which will all be undone with a single
> keystroke. Sort of the equivalent of closing a changeset.

That's just an unclear representation of a composite action! The
RecordAction is undo.push, the FinishUndoBlock is
MainUndoStackaddAction(), and the
carrying-out-before-we're-finished is the bad idea that we need to
avoid.

> But, since I clearly don't understand how this stuff works, these
> impertinent suggestions may be completely off the mark.

Since I barely understand how it all works, but it certainly does
work, I tend to just try to figure it out! When (if) I ever get to the
point where I fully understand it then I might change my mind, but I'm
not there yet.

Cheers,
Andy

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev


Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Steve Bennett
On Fri, Feb 25, 2011 at 11:21 PM, Andy Allan  wrote:
> add a separate undo step for every junction. Since only one keypress
> causes MakeJunction, it should be undoable in one button press.
> MakeJunction should have its own composite action and push things onto
> it when it loops round like this, then passes just one action back to
> the caller.

Err, yes, that was certainly the goal :)

> Oh, and finally, I suspect some of these composite actions should be
> in net/systemeD/connection/actions - especially MakeJunction since
> you've now shown with MagicRoundabout that it's a useful too for other
> actions to build on top of.

Good idea - didn't occur to me.

> Right, well, if this all turns out to be gobbldy-gook don't worry too
> much - I'm happy to rewrite them when I get a chance. If you want to
> give it a go yourself I'm 100% happy to explain further or answer
> questions.

Thanks, I'll have a bit of a read up and explore. The big problem I
was encountering was that I needed the intermediate results of some of
those actions, in order to take later steps. It was no good saying
"after this method, we'll create a circle, then we'll split some
things, then we'll delete some ways. Ok, now go and do that stuff". I
actually needed the split to have taken place so I could identify
which half of the split was the one that I wanted to delete.

I was since then thinking that what I might need was a kind of
CompositeUndoableAction that also carries out the actions straight
away (like the main undo stack), as well as piling them up for later.
As your doc points out:

>A Composite action doesn't actually make any changes itself - it just figures 
>out what list of changes are going to be needed to meet the overall goal.

and

>Although usually irrelevant, this becomes important when iterating over 
>things, and sometimes tricks like iterating backwards over the list become 
>necessary. You can see the ultimate in juggling while working out relations 
>handling during SplitWayAction since you need to insert the new way perhaps 
>multiple times into a given relation, but you need to work out all the insert 
>indexes up-front before any actual inserting is done.

Does this absolutely have to be the case? I can't quite understand,
from a theoretical point of view, why this principle is necessary. Why
not add the action to a stack, and also carry it out now? What's the
benefit of maintaining the current state as long as possible, then
doing all the actions in one flurry?

Also, while I'm irreverently questioning everything:

>There's two conventions that don't help much in understanding, but they are 
>widely used

This is very true. Could we perhaps change them? Maybe rename "undo"
as "actionStack", and rename the ubiquitous "performAction" argument
as "actionStackFunc" or something.

Until last night, I honestly had no idea that actions weren't getting
executed immediately. And even then I only had a suspicion about it,
until I caused an infinite loop with some code that kept trying to
delete a node until it ran out of nodes (which never happened because
they never got deleted). So, I think these conventions are perhaps
beyond quaint and have reached harmful and counterproductive.
(Something about the term "undo" in particular made me assume that we
were piling up a list of actions in case we needed to undo, whereas
actually we're piling up a list of actions *to be done*. The fact that
the user may later undo them is actually quite irrelevant.)


And just to add a bit more incoherent rambling, would a simpler model
be, instead of passing around these stacks everywhere, to have just a
main undo stack with two functions:
1) RecordAction which adds an action to the undo stack, and carries it out.
2) FinishUndoBlock, which groups all actions since the last time it
was called, into one block, which will all be undone with a single
keystroke. Sort of the equivalent of closing a changeset.

But, since I clearly don't understand how this stuff works, these
impertinent suggestions may be completely off the mark.
Steve

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev


Re: [Potlatch-dev] Introducing Magic Roundabout tool

2011-02-25 Thread Andy Allan
On Thu, Feb 24, 2011 at 10:47 PM, Steve Bennett  wrote:

> Whoops, sorry about that. All fixed now.

Cheers.

> I've discovered some slight flakiness, in that occasionally it
> produces a bad result, and if you try to undo, you get an exception.
> I'll need to understand a bit more how to use the undo system.

Regarding the Undo system - while we all hail randomjunk as the only
one who Truly Understands how it works, I've been toiling away as an
apprentice for a while now[1] and pretty much have got to grips with
it. I've taken a few hours this morning to put together a wiki page
with some documentation, that as it says at the top, may or may not
help.

http://wiki.openstreetmap.org/wiki/Potlatch_2/Developer_Documentation/undo

I'd suggest reading that - if by magic it's comprehensible, the next
bit of this email might make more sense.

As for your code, the two^Wthree^Wfour things I can spot straight away are:

1) In DrawWay.as.magicRoundabout() you're adding two different things
to the undo stack - the first and last lines are adding a composite
action called "undo" that doesn't actually do anything. The middle
line asks MagicRoundabout to return its resulting action to the
MainUndoStack. So two ways of achieving similar goals would be:

new MagicRoundabout(currentNode(), elastic.length,
MainUndoStack.getGlobalStack().addAction);

OR

var undo:CompositeUndoableAction = new CompositeUndoableAction("Magic
roundabout");
new MagicRoundabout(currentNode(), elastic.length, undo.push);
MainUndoStack.getGlobalStack().addAction(undo);

... but since the second one would be creating a composite action, and
pushing only one action onto its list, it's a bit redundant.

2) MagicRoundabout.as#30 calls performAction multiple times - instead
you could create a compositeaction, add the new way and all the
splitways to it, and send it back in one go.

3) Assuming you do 2), then when magicroundabout finishes adding
SplitWayActions it'll need to add a MakeJunctions action, to the list
too. But MakeJunctions accesses the undostack directly (line 40) so it
breaks the undo chain. MakeJunction should be rewritten to take a
performAction function and instead of doing things itself, pass the
actions back to the caller.

4) MakeJunction calls MainUndoStack...addAction() in a loop - that'll
add a separate undo step for every junction. Since only one keypress
causes MakeJunction, it should be undoable in one button press.
MakeJunction should have its own composite action and push things onto
it when it loops round like this, then passes just one action back to
the caller.

Oh, and finally, I suspect some of these composite actions should be
in net/systemeD/connection/actions - especially MakeJunction since
you've now shown with MagicRoundabout that it's a useful too for other
actions to build on top of.

Right, well, if this all turns out to be gobbldy-gook don't worry too
much - I'm happy to rewrite them when I get a chance. If you want to
give it a go yourself I'm 100% happy to explain further or answer
questions.

Cheers,
Andy


[1] Like, years, man

___
Potlatch-dev mailing list
Potlatch-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/potlatch-dev