Форум Flasher.ru

Форум Flasher.ru (http://www.flasher.ru/forum/index.php)
-   ActionScript 3.0 (http://www.flasher.ru/forum/forumdisplay.php?f=83)
-   -   Undo/Redo Manager - как лучше сделать ? (http://www.flasher.ru/forum/showthread.php?t=194611)

AlexLucas 18.02.2013 18:32

Undo/Redo Manager - как лучше сделать ?
 
Доброго времени суток.
Пишу визуальный редактор.
Есть задача дать пользователю возможность откатывать свои действия.
По сути на сцене будут геометрические фигуры (Shape) и текстовые контейнеры, над которыми и будут производиться эти действия.
И появился вопрос - какой концепт вот такого Undo Manager'a лучше :

1) До выполнения функции (действия) сохранять состояния всех объектов на сцене и при откате обновлять все объекты на сцене.
Плюс - довольно просто регистрировать изменения, просто делаем скриншот, а при откате циклом проходим по всем объектам и восстанавливаем прежние значения.
Минус - будет храниться очень много данных, ну и возможно обновлять всю сцену (особенно текстовые поля с большим кол-вом текста) может дорого обойтись.

2) Регистрировать только текущие изменения, и объект(ы) который(е) изменился(ись) при выполнении определённых функций (действий).
Плюс - не нужно будет изменять все объекты, не нужно будет хранить инфу о всех объектах на сцене. Кроме того, в будущем надо будет внедрить возможность создания макросов, который по идее будет работать по такому принципу - регистрировать изменения одного объекта и воспроизводить их.
Минус - регистрация изменений будет сложнее, мне кажется. Кроме того, в s:TextArea (которую я использую) уже есть UndoManager, надо будет как-то с ним сотрудничать.

Что посоветуете вы ?

Zebestov 18.02.2013 18:58

3) По сути — в точности п.1, только храним состояние лишь тех объектов, которые изменились. Таким образом имеем абсолютную схему хранения состояний (а не относительную, как в п.3), но при этом такую же экономную (почти такую же).

expl 18.02.2013 19:09

2-й подход однозначно
Т.к. с сохранением состояний мороки тоже не мало,
а производительность и количество возможных отмен - просто сводит его применимость на нет.

Делаем объекты-комманды, которые при применении сохраняют всё что нужно для отмены только своего действия, накидываем в стек и радуемся.
(если растровый редактор делаете, придётся скриншотить участок, на котором рисовали и сохранять в комманде, перед тем как зашить в него нарисованное,
для векторного - там проще, конечно, сохранил ссылку на нарисованный примитив - и всё - можешь удалить по ссылке
если удаляешь примитив - сохраняешь ссылку на родителя и на него, чтобы потом добавить, а родитель к моменту отката будет присутствовать автоматически и т.д и т.п.)

Это кажется сложно - на самом деле при переходе на комманды код даже прозрачнее станет.

Цитата:

3) По сути — в точности п.1, только храним состояние лишь тех объектов, которые изменились. Таким образом имеем абсолютную схему хранения состояний (а не относительную, как в п.3), но при этом такую же экономную (почти такую же).
Выглядит заманчиво, только:
- дебаг такой структуры данных не проще чем дебаг отмены комманд
(кстати как это выглядит - список ссылок на разные состояния? т.е разные состояния должны ссылаться на другие, иначе не будет экономии - т.е. функциональная немутабельная структура данных получается)
- не решён вопрос быстрой отмены, что делать при отмене? Всё стирать и строить заново?
Или усложнять структуру, чтобы она давала последние изменения? - Дык список применённых комманд почти тоже самое делает. Т.е. список комманд - это и есть ваша структура.

AlexLucas 18.02.2013 19:42

С одной стороны, то что предложил Zebestov отлично подходит. С другой стороны, изменения объекта повлекут за собой изменения других объектов.
Например, мы сузили прямоугольник на 20 пихелей. В панели инструментов изменится значение индикатора ширины, если объект находится в группе, изменятся габариты всей группы, в фотошопе , например, возле каждого слоя есть мини-скриншот объектов на слое, вот он также изменится.
Т.е. нужно вызывать сам метод, в который включены уже все эти изменения, а не просто менять свойства объекта.

Или я что-то не так понял из сообщения Zebestova?

Zebestov 18.02.2013 19:52

Хранить нужно только те состояния, которые произвел пользователь. Остальные изменения вычисляются/восстанавливаются.

expl 18.02.2013 19:59

Чем тогда это отличается от 2-го варианта?

Всмысле при сохранении изменений (например, вместе с коммандами) получаем состояния:
1:добавить круг в 0, 0 ->
2:добавить прямоугольник в 0, 0 ->
3:повернуть прямоугольник на 90º->
4:Удалить прямоугольник

Вариант с состояниями:
1:круг(0,0) ->
2:[ссылка на 1:], прямоугольник(0, 0, 0º) ->
3:[ссылка на 1:], прямоугольник(0, 0, 90º) ->
4:[ссылка на 1:]
Как бы получение структуры [3:[ссылка на 1:], прямоугольник(0, 0, 90º)] по предыдущей [2:[ссылка на 1:], прямоугольник(0, 0, 0º)] и действиям пользователя уже выглядит нетривиально

- Как компактно сохранить состояния не сохраняя изменений?
- Как это сделать просто на _нефункциональном_ языке?
- А если таки сохранять изменения, а не данные, то в чём разница?

- Зачем это всё, если можно просто сохранить изменение вместо сохранения состояния? - И вычислять ничего не надо будет - никаких дифов между старым и новым снимать не надо

Zebestov 18.02.2013 20:04

Для меня сохранить изменения, скажем, позиции — это команда MOVE с относительными данными, а сохранить состояние — это команда POSITION с абсолютными данными. Возможно, я неверно понял автора и, стало быть, мое предложение — это и есть вариант два.

expl 18.02.2013 20:20

Ну часть состояния может сохраняться для нужд отмены (оно будет частью "данных изменения")
но это не то же самое, что сохранять вообще всё состояние кусками для экономии.

Добавлено через 51 минуту
Да, таки есть 3-й _узкоспециализированный_ подход, работает, когда:
- нельзя исправлять нарисованное
- много графики
Суть:
- при рисовании новой линии запоминаем её параметры (координаты точек) и ложим в список
- добавляем новый спрайт, рисуем там линию
- при добавлении 100-го спрайта удаляем первый из списка и отрисовываем его содержимое в _растровую_ подложку - для повышения производительности отрисовки
- при откате просто удаляем спрайты
- только когда дойдем до 1-го спрайта - берём стираем все из растровой подложки и рисуем всё сначала по сохранённым данным
- получается, что тормоза наступают только после 100-й отмены и вцелом редактор не тормозит при добавлении графики.
Но это работает, только при запрете правок уже нарисованного.

AlexLucas 18.02.2013 21:13

То есть мне полюбому в каждой функции которая будет производить какие-то перемены, нужно будет создавать новую команду и добавлять её в стак ?
А если я эту функцию буду вызывать из Undo/Redo, у меня создастся автоматом другая redo/undo комманда, да и как отличить когда вызывается комманда из undo или из redo? Добавить дополнительные параметры в функцию ?
Или для каждого типа операций создавать свой класс комманды ? Как тут ?
Я так понял именно так делается в паттерне Command?

expl 18.02.2013 21:25

Цитата:

То есть мне полюбому в каждой функции которая будет производить какие-то перемены, нужно будет создавать новую команду и добавлять её в стак ?
Угу, ничего страшного в этом нет - передаем комманде контекст (со всем необходимым) при создании, чтобы она там ковырялась - чтобы не передавать параметры каждый раз - и всё не так сложно становится

Цитата:

А если я эту функцию буду вызывать из Undo/Redo, у меня создастся автоматом другая redo/undo комманда, да и как отличить когда вызывается комманда из undo или из redo? Добавить дополнительные параметры в функцию ?
- Не надо делать вызовы других комманд внутри комманд. И сами функции Undo и Redo коммандами делать смысла тоже нет.
(На самом деле можно создавать комманду и вызывать execute внути другой комманды, но _не_ добавлять при этом её в список отмены - самому вызывать undo этих вложенных комманд внутри своего undo. Т.е. вложенные комманды не палятся в общем списке)
- Не надо внутри Command::execute добавлять себя в список отмены - лучше сделать другую функцию в контроллере или UndoManager'е:
Код AS3:

public function executeCommand(command:Command):void
{
    // Если надо поддерживать redo - то сюда ещё добавляется код по отбрасыванию отменённого
    // (Раз мы сделали действие - всё что отменили стирается и повторить обратно его должно быть нельзя,
    // этож не система контроля версий - чтобы ветки городить)
    // Но здесь без redo
    _undoList.push(command);
    command.init(_context);
    command.execute();
}
 
public function undo():void
{
  var command:Command = _undoList.pop();
  if (command != null)
  {
      command.undo();
  }
}

И всё станет просто и понятно.
А специфичные параметры комманды передаём в конструктор:
Код AS3:

_undoManager.execute(new RotateCommand(object, rotateAngle));


yasha005 19.02.2013 00:40

Цитата:

Сообщение от expl (Сообщение 1121161)
Чем тогда это отличается от 2-го варианта?

Всмысле при сохранении изменений (например, вместе с коммандами) получаем состояния:
1:добавить круг в 0, 0 ->
2:добавить прямоугольник в 0, 0 ->
3:повернуть прямоугольник на 90º->
4:Удалить прямоугольник

Вариант с состояниями:
1:круг(0,0) ->
2:[ссылка на 1:], прямоугольник(0, 0, 0º) ->
3:[ссылка на 1:], прямоугольник(0, 0, 90º) ->
4:[ссылка на 1:]
Как бы получение структуры [3:[ссылка на 1:], прямоугольник(0, 0, 90º)] по предыдущей [2:[ссылка на 1:], прямоугольник(0, 0, 0º)] и действиям пользователя уже выглядит нетривиально

- Как компактно сохранить состояния не сохраняя изменений?
- Как это сделать просто на _нефункциональном_ языке?
- А если таки сохранять изменения, а не данные, то в чём разница?

- Зачем это всё, если можно просто сохранить изменение вместо сохранения состояния? - И вычислять ничего не надо будет - никаких дифов между старым и новым снимать не надо

Как вариант воспользоваться одним из поведенческих шаблонов проектирования. Например шаблон "Состояние".

Состоит из следующих компонентов:
Код:

1. Интерфейс состояния
2. Контекстный класс
3. Классы контекста состояния
4. Клиент

Код AS3:

package 
{
        //State Machine Interface
        // Интерфейс автомата состояний
        interface State
        {
                function startPlay():void;
                function stopPlay():void;
        }
}

Код AS3:

package 
{
        //Context class
        // Контекстный класс
        class VideoWorks
        {
                var playState:State;
                var stopState:State;
                // Переменная для хранения текущего состояния
                var state:State;
 
                public function VideoWorks()
                {
                        trace("Video Player is On");
                        playState = new PlayState(this);
                        stopState = new StopState(this);
                        state = stopState;
                }
                public function startPlay():void
                {
                        state.startPlay();
                }
                public function stopPlay():void
                {
                        state.stopPlay();
                }
 
                // Методы получатели и установщики
                public function setState(state:State):void
                {
                        trace("A new state is set");
                        this.state = state;
                }
                public function getState():State
                {
                        return state;
                }
                public function getPlayState():State
                {
                        return this.playState;
                }
                public function getStopState():State
                {
                        return this.stopState;
                }
        }
}

Код AS3:

package 
{
        //Play State
        // Класс контекстного состояния: состояние проигрывания
        class PlayState implements State
        {
                var videoWorks:VideoWorks;
                public function PlayState(videoWorks:VideoWorks)
                {
                        trace("--Play State--");
                        this.videoWorks = videoWorks;
                }
                public function startPlay():void
                {
                        trace("You're already playing");
                }
                public function stopPlay():void
                {
                        trace("Stop playing.");
                        videoWorks.setState(videoWorks.getStopState());
                }
        }
}

Код AS3:

package 
{
        //Stop State;
        // Класс контекстного состояния: проигрывание остановлено
        class StopState implements State
        {
                // Доступ к методам установщикам и получателям для каждого состояния
                var videoWorks:VideoWorks;
 
                public function StopState(videoWorks:VideoWorks)
                {
                        trace("--Stop State--");
                        this.videoWorks = videoWorks;
                }
                public function startPlay():void
                {
                        trace("Begin playing");
                        videoWorks.setState(videoWorks.getPlayState());
                }
                public function stopPlay():void
                {
                        trace("You're already stopped");
                }
        }
}

Код AS3:

package 
{
        //Test states
        // Тестируем состояния: Клиент
        import flash.display.Sprite;
        public class TestState extends Sprite
        {
                public function TestState( ):void
                {
                        var test:VideoWorks = new VideoWorks( );
                        test.startPlay( );
                        test.startPlay( );
                        test.stopPlay( );
                        test.stopPlay( );
                }
        }
}


expl 19.02.2013 01:08

Есть такой паттерн "состояние", да
Наверно делать с его помощью проигрыватель - не самая худшая идея - да.

Но куда Вы его в систему отмены/повтора впихнуть предлагаете?!

В редакторе есть еще непосредственно система редактирования, т.е. определения действий - там да, для переключения инструментов, состояния процесса редактирования всяко лучше свичей построить набор состояний.
Но здесь то речь про _применение_ и _отмену_ этих изменений.

Хочется видеть то не копипасту канонического примера из книги, а пример использования в данной задаче.

yasha005 19.02.2013 01:14

Цитата:

Сообщение от expl (Сообщение 1121215)
Есть такой паттерн "состояние", да
Наверно делать с его помощью проигрыватель - не самая худшая идея - да.

Но куда Вы его в систему отмены/повтора впихнуть предлагаете?!

В редакторе есть еще непосредственно система редактирования, т.е. определения действий - там да, для переключения инструментов, состояния процесса редактирования всяко лучше свичей построить набор состояний.
Но здесь то речь про _применение_ и _отмену_ этих изменений.

Хочется видеть то не копипасту канонического примера из книги, а пример использования в данной задаче.

Вы расчитываете что кто-то за вас будет делать вашу работу? Мне думается врятли тут найдутся такие желающие. На самом деле данный шаблон можно впихнуть в любую реализацию где требуется переключение состояний в том числе и переключение инструментов: ведь в определённый момент времени юзается только один инструмент

expl 19.02.2013 01:23

Цитата:

Вы расчитываете что кто-то за вас будет делать вашу работу?
У меня здесь какая-то работа появилась O_o

Я к чему это всё написал:
- Вы процитировали все мои слова про состояние редактируемых данных/графики (_не_ редактора!),
про его откат, про продходы сохранения изменений и сохранения всего состояния данных применительно к откатам.
- Потом привели пример паттерна "состояние" очень похожий на пример из книги "ActionScript 3.0. Шаблоны проектирования".
- Как применить этот шаблон к откатам совершенно не понятно
- Поэтому хочется увидеть как паттерн состояние работает внутри системы откатов/повторов (мне чисто из-за любопытства - ибо задачи в данный момент построить редактор не стоит)

Или Вы имели в виду только "переключение инструментов"?

wvxvw 19.02.2013 01:25

На мой взгляд, самый надежный вариант позволяющий и усложнятся и макросы писать, и вообще переводит работу с редактором на новый уровень... это когда команды редактор выполняет не непосредственно, а через прослойку интерпретируемого языка (для сравнения, Vim и Emacs так примерно работают, CAD / Photoshop - похоже).
В таком случае пользователь работая с редактором пишет программу, которую потом можно и воспроизвести, и сохранить, или, например, есть вот такая штука (использую, очень нравится): http://www.emacswiki.org/emacs/UndoTree (т.е. можно хранить одновременно несколько веток изменений и потом выбирать какую именно использовать - гораздо круче, чем, когда один шаг undo + новая команда затирают десяток undo сделанных до того).

expl 19.02.2013 01:34

Цитата:

это когда команды редактор выполняет не непосредственно, а через прослойку интерпретируемого языка
wvxvw, ведь для этого надо API навернуть на свою систему. Я даже для тестовых прогонов тутора в нашей игрушке не смог скрипт задействовать - слишком много вокруг as3-кода всё равно пишется, на самом as3 в разы проще.
Если макросы пользователю редактора не нужны, то какой в этом смысл?
Цитата:

можно хранить одновременно несколько веток изменений и потом выбирать какую именно использовать
Всегда думал, дойдет ли кто до такого - всётки дошли :) Но много ли в этом смысла без мержинга между ветками?
Хотя есть - скопипастить оттуда-отсюда.

wvxvw 19.02.2013 01:57

В чем смысл:
Я вот сейчас "прохожу перепрофилировку" в "на все руки мастер (но восновмом, Руби, наверное, хочется верить, возможно, но пока нет, а в оставшееща время ПХП / АСП классик / Жабоскрипт и прилагающаяся муть)".

Возникают два типа штатных ситуаций:
- ХТМЛ - очень предательская технология, с напрочь отсутствующей логикой, где сносный результат достигается только бесконечным повтором попыток и ошибок. Работая с файлом приходитыся очень часто делать несколько изменений - потом проверять, потом недомуветь от результатов, потом возвращаться и переделывать, и так по многу раз. (Сегодня пару часов убил пытаясь понять почему у кнопки два раза срабатывает onclick - оказалось label рядом с кнопкой диспатчил каким-то образом событие в соседнюую кнопку...
- Жабоскрипт "программисты", наученные горьким опытом даже не ищут логику в происходящем, а руководствуются "джаваскрипт чутьем", которе им подсказывает, что и где поменять, чтобы "заработало". Иногда приходится просить помощи у старшего товарища, а потом, после того как "заработало", откатывать множество бесполезных изменений сделанных старшим товарищем. :)

ЗЫ. Мержить можно: ediff - сразу же и мержит, но до такой степени не доходило, пока.

yasha005 19.02.2013 10:02

Цитата:

Сообщение от expl (Сообщение 1121215)
...
В редакторе есть еще непосредственно система редактирования, т.е. определения действий - там да, для переключения инструментов, состояния процесса редактирования всяко лучше свичей построить набор состояний.
Но здесь то речь про _применение_ и _отмену_ этих изменений. ...

Это конечно хорошо, что техническое задание вы уже составили. Теперь давайте разберёмся как можно применить шаблон, а возможно и шаблоны проектирования к вашему техническому заданию.

Если вам всётаки интересно моё мнение каким образом я вижу данную реализацию в шаблоне:
В данном случае я вижу два состояния:
1. Переход на следующее дествие
2. Переход на предыдущее действие
А в качестве вспомогательного класса можно использовать класс реализованный на основе шаблона "Декоратор". Как вы помните из книжечки Декоратор добавляет объекту обязанностей. Он упаковывает его в свою новую функциональность и свойства, в то время как внутрення структура объекта остаётся неизменной.
Таким образом я предлагаю своего рода упаковывать каждое действие в новое действие, а чтобы не возникло путаницы является ли это новым действием или отменой действий, за это как раз и будет отвечать основная реализация Состояния.

zuxul 19.02.2013 10:33

Цитата:

Сообщение от yasha005 (Сообщение 1121246)
Это конечно хорошо, что техническое задание вы уже составили. Теперь давайте разберёмся как можно применить шаблон, а возможно и шаблоны проектирования к вашему техническому заданию.

Знатно троллите expl :taunt:

yasha005 19.02.2013 10:54

Цитата:

Сообщение от zuxul (Сообщение 1121247)
Знатно троллите expl :taunt:

Напрасно вы так думаете, с моей стороны - вполне конструктивный совет. А способ его релизации уже зависит от фантазии и понимании самих шаблонов.

Я считаю as3 достаточно гибким языком, потому как можно реализовать сию конструкцию не только расширением наследования классов и расширением реализации интерфейсов, но и передачи самого класса в экземпляр другого класса посредством ключевого слова this

iNils 19.02.2013 11:23

Цитата:

Сообщение от yasha005 (Сообщение 1121246)
Это конечно хорошо, что техническое задание вы уже составили. Теперь давайте разберёмся как можно применить шаблон, а возможно и шаблоны проектирования к вашему техническому заданию.

А вы в курсе, что автор темы не expl?

yasha005 19.02.2013 11:54

Цитата:

Сообщение от iNils (Сообщение 1121252)
А вы в курсе, что автор темы не expl?

Тот факт, что народ на форуме отзывчивый и помогли человеку составить техническое задание - респект и уважуха форуму и его создателям

iNils 19.02.2013 12:12

Цитата:

Сообщение от yasha005 (Сообщение 1121255)
Тот факт, что народ на форуме отзывчивый и помогли человеку составить техническое задание - респект и уважуха форуму и его создателям

Причем тут это? Вы общаетесь с expl так, как будто он автор темы и ему нужна помощь. Вот я и спрашиваю, вы в курсе, что помощь нужна совсем другому человеку?

yasha005 19.02.2013 12:26

Цитата:

Сообщение от iNils (Сообщение 1121257)
Причем тут это? Вы общаетесь с expl так, как будто он автор темы и ему нужна помощь. Вот я и спрашиваю, вы в курсе, что помощь нужна совсем другому человеку?

я так понял, что expl представляет интересы AlexLucas, поэтому у меня сложилось мнение, что expl ведёт тему от имени AlexLucas. Чем и обусловлен данный диалог как с автором сабжа

AlexLucas 19.02.2013 13:26

Цитата:

я так понял, что expl представляет интересы AlexLucas
Вы неправильно поняли :)

@expl,
Большое спасибо, попробую реализовать.


Часовой пояс GMT +4, время: 06:11.

Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.