Рекомендации по переходу от вложенного состояния к вложенному состоянию (см. схему)

Я пытаюсь понять, как лучше всего реализовать вложенные переходы между состояниями в однопоточном языке программирования (Actionscript). Скажем, у меня есть структура, подобная этому дереву поведения: дерево поведения

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

Итак, если бы мы находились в самом нижнем левом конечном узле и хотели перейти к самому нижнему правому конечному узлу, нам пришлось бы:

  • переход из нижнего левого узла
  • по завершении (скажем, после секунды анимации), переход от родителя,
  • по завершении, переход от родителя
  • по завершении переход В самый правый родитель
  • по завершении переход в самый правый дочерний элемент
  • в комплекте, переход в листе

Мой вопрос:

Если вы представляете каждый из этих узлов как представления HTML (где листья являются «частичными», термин заимствован из rails) или представления MXML, где вы вкладываете подкомпоненты, и вам не обязательно знать уровни вложения из корень приложения, как лучше всего анимировать переход, как описано выше?

Один из способов — сохранить все возможные пути глобально, а затем сказать «Приложение, переход от этого пути, переход по этому пути». Это работает, если приложение очень простое. Вот как это делает Gaia, фреймворк ActionScript. Но если вы хотите, чтобы он мог переходить в/из произвольно вложенных путей, вы не можете хранить это глобально, потому что:

  1. Actionscript не мог справиться со всей этой обработкой
  2. Не похоже на хорошую инкапсуляцию

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

Другим возможным решением было бы просто сказать: «Приложение, переход от предыдущего дочернего узла, а когда это будет завершено, переход к текущему дочернему узлу», где «дочерний узел» является прямым дочерним элементом корня приложения. Затем самый левый дочерний элемент корня приложения (у которого есть два дочерних узла, каждый из которых имеет два дочерних узла) будет проверять, находится ли он в правильном состоянии (если его дочерние элементы «перешли»). Если нет, он вызовет для них "transitionOut()"... Таким образом, все будет полностью инкапсулировано. Но похоже, что это будет довольно интенсивно процессор.

Что вы думаете? У вас есть другие альтернативы? Или вы можете указать мне какие-либо хорошие ресурсы по дереву поведения ИИ или иерархическому State Machines, которые описывают, как они практически реализуют асинхронные переходы между состояниями:

  • Откуда они вызывают "transitionOut" на объекте? Из корня или конкретного ребенка?
  • Где хранится состояние? Глобально, локально? Какова область, определяющая, что вызывает «transitionIn()» и «transitionOut()»?

Я видел/читал много статей/книг по ИИ и конечным машинам, но мне еще предстоит найти что-то, описывающее, как они на самом деле реализуют асинхронные/анимированные переходы в сложном объектно-ориентированном проекте MVC с сотнями представлений/графиков, участвующих в дереве поведения.

Должен ли я вызывать переходы из самого родительского объекта или из дочернего?

Вот некоторые из вещей, которые я изучил:

Хотя это не обязательно проблема ИИ, нет других ресурсов, описывающих, как применять архитектуры вложенных состояний к веб-сайтам; это самые близкие вещи.

Другой способ сформулировать вопрос: как вы транслируете изменения состояния в приложение? Где вы держите слушателей событий? Как определить, какое представление анимировать, если оно произвольно вложено?

Примечание. Я не пытаюсь создать игру, я просто пытаюсь создавать анимированные веб-сайты.


person Lance Pollard    schedule 18.01.2010    source источник
comment
всегда ли древовидная структура постоянна или может меняться в зависимости от пути, который выбрал пользователь?   -  person Anurag    schedule 18.01.2010
comment
древовидная структура всегда постоянна. но количество узлов может измениться (скажем, в одной галерее 10 изображений, а в другой 100). в основном мы заранее знаем возможные структуры (какие родители могут иметь каких детей), но мы не обязательно знаем число или уровень глубины.   -  person Lance Pollard    schedule 18.01.2010


Ответы (3)


Прежде чем предлагать возможный подход, я попытаюсь упростить проблему. Переходы кажутся связанными с представлениями, а не с моделью. Если бы я пропустил переходы и сразу перешел к какому-то другому конечному узлу, приложение все еще работало бы, но для пользователей не было бы визуальной подсказки. Поэтому я предлагаю вам использовать контроллер представления специально для хранения текущей ветки и переходов различных представлений.

Вероятно, лучше разделить два типа переходов на своего рода стек, в котором всплывающий переход должен вернуться к предыдущему узлу, а толчковый переход идет вперед в иерархии. Apple использует аналогичную технику для управления навигационными приложениями с помощью контроллер представления навигации. По сути, он поддерживает стек контроллеров представления, которым пользователь следовал, чтобы добраться до определенного узла. Когда пользователь возвращается, верхний элемент извлекается из стека, и пользователь видит предыдущий элемент. Если пользователь углубляется в иерархию, новый контроллер представления помещается в стек.

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

Если пользователь перешел от одного конечного узла к другому, текущий стек будет извлечен до общего родителя. Затем глобальной древовидной структуре будет предложено получить путь от этого родителя до нового конечного узла. Этот путь узлов помещается в текущий стек навигации, и при перемещении каждого элемента отображается переход.

В простом алгоритме были бы эти две структуры данных и способ получить всю ветвь, содержащую конечный узел:

  1. глобальное представление дерева
  2. стек текущей ветки

изначально:

stack = []
tree = create-tree()

алгоритм:

// empty current branch upto the common ancestor, root node if nothing else
until stack.peek() is in leaf-node.ancestors() {
    stack.pop() // animates the transition-out
}
parent = stack.peek();
// get a path from the parent to leaf-node
path = tree.get-path(parent, leaf-node)
for each node in path {
    stack.push(node) // animates the transition-in
}

Обновление:

Все приложение может иметь один навигационный контроллер или несколько таких контроллеров. Контроллеру навигации нужно только знать, что существует дерево, которое предоставляет определенные операции. Таким образом, вы можете создать интерфейс с этими операциями и позволить конкретным подклассам реализовать это.

Я могу думать только о двух операциях, которые необходимо раскрыть (псевдо-Java-синтаксис):

interface Tree {
    List<Node> getAncestors(node);
    List<Node> findPath(ancestorNode, descendantNode);
}

Это должно обеспечить достаточную абстракцию, чтобы навигационный контроллер оставался внутри глобальной древовидной структуры. Чтобы перейти на следующий уровень, используйте внедрение зависимостей, чтобы объект дерева внедрялся в навигацию. контроллер, улучшая тестируемость и полностью разрывая связь с деревом.

person Anurag    schedule 18.01.2010
comment
эй, чувак, спасибо за этот отличный ответ и ссылку на iphone! это делает меня намного ближе. поэтому мой вопрос в ответ на это: как получить доступ к этому глобальному дереву, чтобы приложение оставалось модульным и масштабируемым? - person Lance Pollard; 18.01.2010
comment
круто .. я обновил ответ, добавив дополнительную информацию о глобальном дереве. - person Anurag; 18.01.2010

Я просто предполагаю, но вы можете сохранить это дерево в реальном дереве в своем коде, а затем, когда вы щелкнете, вы хотите перейти, вы вызовете функцию, которая называется, скажем, findPath(fromNode, toNode), эта функция найдет путь между два узла, один из способов сделать это с помощью этого псевдокода:

Define array1;
Define array2;

loop while flag is false {
    store fromNode's parent node in array1;
    store toNode's parent node in array2;

    loop through array1 {
        loop through array2 {
            if array1[i] == array2[j] {
             flag = true;
            }
        }
    }
}

path = array1 + reverse(array2);

И там твой путь.

ОБНОВЛЕНИЕ:

Другим решением было бы сделать так, чтобы каждая страница имела что-то вроде этого псевдокода — я начинаю очень любить псевдокод:

function checkChildren(targetNode) {
    loop through children {
        if children[i] == target {
            return path;
        }
        if children[i].checkChildren == target node {
            return path;
        }
    }
}

Это очень-очень абстрактно, в нем гораздо больше деталей и много оптимизаций, вот что я имею в виду:

  • Передайте путь, идущий вверх по дереву, функции, чтобы путь можно было найти напрямую, а не возвращаться к вызывающей функции.
  • Передайте текущий вызывающий дочерний элемент родительскому элементу checkChildren, чтобы он не утруждал себя проверкой этого дочернего элемента, чтобы ускорить проверку.

Я все еще думаю, что я не донес свою мысль хорошо, поэтому я попытаюсь объяснить это.

Скажем, вы переходите от pic2 в галерее к project3 в проектах.

pic2 будет проверять своих дочерних элементов (если они есть), если он находит цель, он идет туда, в противном случае он вызывает проверку своего родителя (галереи). Дети, передавая себя родителю, не будут проверять его, и передавая себя родителю в массив, чтобы родитель знал, какой путь был выбран.

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

Если ни один из его прямых дочерних элементов не является целью, то он вызывает каждый из своих дочерних элементов checkChildren, добавляя себя в массив пути, так что, если какой-либо из его дочерних элементов находит цель, он добавляет себя и дочерний элемент в массив, и у вас есть свой дорожка.

Если ни один из дочерних элементов не находит целевую галерею, вызывает родительскую (домашнюю страницу) проверку дочерних элементов, передавая себя и добавляя себя в массив пути.

Домашняя страница проверяет все свои дочерние элементы, кроме галереи, используя тот же метод.

В качестве альтернативы вы можете не передавать путь в виде массива, а вместо этого просто перейти на родительскую страницу, если ни один из дочерних или дочерних элементов не совпадает, потому что вы уверены, что цель не находится на этой странице.

Надеюсь, я ясно выразился. Я могу написать для вас checkChildren, если хотите.

person Leo Jweda    schedule 18.01.2010
comment
эй, спасибо. Я делаю что-то подобное сейчас, когда у меня есть центральный менеджер, который просматривает иерархию объектов отображения, основываясь на предварительно определенном наборе путей, чтобы найти, какие объекты следует переходить из одного в другой. Это работает, но я Я искал что-то более абстрактное, поэтому мне не нужно было централизованно хранить или вычислять эту информацию о пути. Или это предпочтительный метод? - person Lance Pollard; 18.01.2010
comment
Я не думаю, что сохранение самих путей - хорошая идея, потому что вы получите пути (n-1)+(n-2)..1, поэтому для дерева, которое вы нарисовали в вопросе, вы в итоге получается 55 путей, плюс, я думаю, что проще заставить компьютер делать работу за вас ;) Если вы хотите, вы можете получить все пути и кэшировать их, это может занять больше ресурсов, но это упростит поиск пути, когда нужно быстрее.. Это то, что вы делаете? Кроме того, вы можете попробовать мое новое решение. - person Leo Jweda; 18.01.2010

либо я не совсем понимаю вашу проблему, либо вы слишком усложняете ее... постарайтесь мыслить просто, потому что в основном все, что вы хотите сделать, это следующее:

  1. получить путь в дереве
  2. выполнять несколько анимаций последовательно (вдоль этого самого пути)

ни одна из этих вещей не слишком сложна. для последней задачи вы, вероятно, можете использовать одну из лучших анимационных библиотек AS3. но для понимания позвольте мне предложить вам легкое решение:

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

Итак, вот код:

во-первых, давайте определим интерфейс для всех ваших листьев/узлов... следующее должно помочь:

package  {
    public interface ITransitionable {
     //implementors are to call callback when the show/hide transition is complete
     function show(callback:Function):void;
     function hide(callback:Function):void;
     function get parent():ITransitionable;
    }
}

теперь следующий класс сможет выполнять переходы, как вы указали, на деревьях ITransitionable:

package  {
 public class Transition {
  ///Array of animation starting functions
  private var funcs:Array;
  ///Function to call at the end of the transition
  private var callback:Function;
  public function Transition(from:ITransitionable, to:ITransitionable, callback:Function) {
   this.callback = callback;
   this.funcs = [];
   var pFrom:Array = path(from).reverse();
   var pTo:Array = path(to).reverse();
   while ((pFrom[0] == pTo[0]) && (pTo[0] != null)) {//eliminate common path to root
    pFrom.shift();
    pTo.shift();
   }
   pFrom.reverse();//bring this one back into the right order
   //fill the function array:
   var t:ITransitionable;
   for each (t in pFrom) this.funcs.push(hider(t));
   for each (t in pFrom) this.funcs.push(shower(t));
   this.next();
  }
  ///cancels the overall transition
  public function cancel():void {
   this.funcs = [];
  }
  ///creates a function that will start a hiding transition on the given ITransitionable.
  private function hider(t:ITransitionable):Function {
   return function ():void {
    t.hide(this.next());
   }
  }
  ///@see hider
  private function shower(t:ITransitionable):Function {
   return function ():void {
    t.show(this.next());
   }
  }
  ///this function serves as simple callback to start the next animation function
  private function next(...args):void {
   if (this.funcs.length > 0) this.funcs.shift()();
   else this.callback();
  }
  ///returns the path from a node to the root
  private function path(node:ITransitionable):Array {
   var ret:Array = [];
   while (node != null) {
    ret.push(node);
    node = node.parent;
   }
   return ret;
  }
 }

}

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

person back2dos    schedule 18.01.2010