Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





¿Qué os parece este método para hacer un Undo?

Iniciado por XÑA, 11 de Febrero de 2011, 12:37:34 PM

« anterior - próximo »

blau

Cita de: Vicente en 14 de Febrero de 2011, 03:04:10 PM
En C#, las clausuras capturan las variables, no sus valores, por eso le pasa a XÑA que captura X, pero como es un atributo y se modifica, luego cuando la lambda accede a X se encuentra otro valor que el esperado. En Java por ejemplo se capturan los valores y no las variables.

Y por eso el truco de usar variables locales, la variable local se captura al igual que el atributo, pero te aseguras que nadie la va a cambiar (ya que no se ve fuera del método, y si vuelves a llamar a ese método se crea una variable local nueva diferente).

Me alegro de saber en detalle a que se debe, pero dejas mi pregunta todavia sin responder.  Y es si hay alguna forma de decirle al compilador que la variable que le pasas al metodo lambda debe cogerla por valor, o algo asi como una copia.


@xña: si, cada vez que generas una accion estas definiendo un nuevo lambda... pero y ¿lo que te ahorras en programacion? si esto hubiese que hacerlo a pelo habria que liar tela... y asi es super elegante, en la clausura se guardan todas las variables afectadas de una forma transparente y supercomoda.

blau

#16
Uff, estoy muy espeso y no pillo el ultimo codigo.

Te pongo el mio entero:

Clase HelperUndo.cs

public class HelperUnDo : HelperComponentScene
   {
       
       public static HelperUnDo Instancia { get; private set; }

       class Data
       {
           public string Operation;
           public Action Undo;
           public Action Redo;

           public override string ToString()
           {
               return Operation;
           }
       }

       FormUndo form = new FormUndo();

       HelperUnDo()
       {
           Instancia = this;
           InputManager.Attach(Game);
           //form.show();
       }

       Stack<Data> UndoActions = new Stack<Data>();
       Stack<Data> RedoActions = new Stack<Data>();

       public static void AddUnDo(string operation, Action undo, Action redo)
       {
           Data data = new Data() { Operation=operation, Redo = redo, Undo = undo };
           Instancia.UndoActions.Push(data);
           Instancia.OnUndoPush(data);
       }
       
       public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
       {
           if (UndoActions.Count>0 && InputManager.Instancia.Keyboard.KeyHit(Microsoft.Xna.Framework.Input.Keys.Z) && InputManager.Instancia.Keyboard.KeyPressed(Microsoft.Xna.Framework.Input.Keys.LeftControl))
           {
               Data data = UndoActions.Pop();
               RedoActions.Push(data);
               data.Undo.Invoke();
               OnUndoPop(data);
           }
           else if (RedoActions.Count > 0 && InputManager.Instancia.Keyboard.KeyHit(Microsoft.Xna.Framework.Input.Keys.Y) && InputManager.Instancia.Keyboard.KeyPressed(Microsoft.Xna.Framework.Input.Keys.LeftControl))
           {
               Data data = RedoActions.Pop();
               UndoActions.Push(data);
               data.Redo.Invoke();
               OnUndoPush(data);
           }

           base.Update(gameTime);
       }
       
       private void OnUndoPop(Data data)
       {
           form.ListUndo.Items.Remove(data);
       }

       private void OnUndoPush(Data data)
       {
           form.ListUndo.Items.Add(data);
       }
   }

Ejempo de uso al final de un drag en el editor:

Vector2 BeginDraggingPosition = beginDragVector2;
Vector2 EndDraggingPosition = Transform.Position;                
HelperUnDo.AddUnDo("Transform Translation", delegate() { Transform.Position = BeginDraggingPosition; }, delegate() { Transform.Position = EndDraggingPosition; });                

Vicente

Cita de: blau en 15 de Febrero de 2011, 03:13:10 PM
Me alegro de saber en detalle a que se debe, pero dejas mi pregunta todavia sin responder.  Y es si hay alguna forma de decirle al compilador que la variable que le pasas al metodo lambda debe cogerla por valor, o algo asi como una copia.

Nope.

Vicente

blau por curiosidad porque has usado un delegado y no una lambda?

Vicente

Y respecto a tu duda XÑA, poco que añadir al código de blau, está de pm :)

blau

Cita de: Vicente en 15 de Febrero de 2011, 04:12:24 PM
blau por curiosidad porque has usado un delegado y no una lambda?

He de confesar de que cuando hice aquello los lambda y yo no nos llevavamos
,aunque ahora admito en este caso que solo es una sentencia lo hubiese hecho con un lambda. :)

por cierto,  a la hora de la verdad serian equivalentes, ¿no?

delegate () { a = b}   <=>  () => { a = b}

reduzio

Hola! Aca les paso como funciona mi sistema de undo/redo, citado de cuando lo postie en el foro de ADVA, esta en C++ pero nada impide hacerlo en java, C# u otro lenguaje (de hecho, el mas dificil es C++). Es muy practico, se usa muy poco codigo y no requiere escribir clases para cada comportamiento (template magic), pero puede llevar unas leidas entenderlo.
Lo utilice en proyectos importantes y doy fe que es muy practico, pero probablemente hay que pensar hacer las APIs para que sean compatibles. Espero que sea de utilidad!

Citar


La alternativa que planteo se basa en usar dynamic typing en los lenguajes que lo soportan, o variants/template magic en C++.
La idea es que, en vez de crear una clase por cada operacion, hay que hacer que cada comando recuerde una lista de llamadas a funciones/metodos con sus respectivos parametros. Si el codigo esta bien encapsulado, esto deberia funcionar en la mayoria de los casos (o todos). Ejemplo:

undo_redo->begin_comand()
undo_redo->add_do_method( sprite, &Sprite::set_pos, 20, 20 );
undo_redo->add_undo_method( sprite, &Sprite::set_pos, 5, 5);
undo_redo->end_command();

Y listo! Esto es muy comodo porque sirve para modificar codigo existente para que pueda hacer undo/redo, sin tener que reencapsular todo el codigo en clases o comandos.

Algunos Problemas..

Un problema comun al hacer un sistema de undo/redo es que hacer cuando hay que poner en un stream, nuevas instancias (o borrar existentes).
Lo mas comodo es guardar comandos trabajando sobre punteros, asi que lo ideal es no guardar comandos que "crean" instancias, sino crear las instancias de forma externa y luego usarlas en comandos, ejemplo:

Se intenta crear un sprite y ponerlo en el sprite_manager:

sprite = new Sprite;

undo_redo->begin_comand()
undo_redo->add_do_method( sprite_manager, &SpriteManager::add_sprite, sprite );
undo_redo->add_undo_method( sprite_manager, &SpriteManager::remove_sprite, sprite );
undo_redo->add_do_reference(sprite);
undo_redo->end_comand();

En C o C++ el comando tiene que guardarse el puntero a sprite, ya que si en algun momento el usuario decide hacer undo, y luego proceder de otra forma, la referencia al sprite se va a perder (a menos que sea un autopointer o un lenguaje dinamico) y debera ser borrado (con delete).

De forma opuesta sucede cuando se retira un sprite y se borra:

undo_redo->begin_comand()
undo_redo->add_do_method( sprite_manager, &SpriteManager::remove_sprite, sprite );
undo_redo->add_undo_method( sprite_manager, &SpriteManager::add_sprite, sprite );
undo_redo->add_undo_reference(sprite);
undo_redo->end_comand();

En este caso "sprite" se guarda como undo reference. Esto sucede porque las listas de undo/redo son te tamanio fijo (ejemplo, hasta 50 operaciones) y cuando los comandos llegan al final de la lista, son eliminados. De esta forma, un objeto que fue removido, es realmente eliminado cuando el comando se elimina.

Vicente

Cita de: blau en 15 de Febrero de 2011, 04:22:35 PM
por cierto,  a la hora de la verdad serian equivalentes, ¿no?

delegate () { a = b}   <=>  () => { a = b}

Se me había pasado esta pregunta. La respuesta corta es "a veces".

La respuesta larga es un poco mas complicada. Primero hay que saber que una lambda puede representar tanto un delegado como un arbol de expresiones, por ejemplo:

Citar
Action act = () => Console.WriteLine(a);
Expression<Action> exp = () => Console.WriteLine(a);

En .NET el ejemplo mas claro de esto es LINQ to Objects, donde se utiliza IEnumerable<T> y los metodos de LINQ reciben delegados, frente a LINQ to SQL, donde se utiliza IQueriable<T> y los metodos de LINQ reciben arboles de expresiones (que luego se traducen a SQL).

Pero hay que tener en cuenta que no todas las lambdas pueden traducirse a arboles de expresiones, lo siguiente no compila:

Citar
Action act2 = () => a = 10;
Expression<Action> exp2 = () => a = 10; // Error

Los arboles de expresiones no pueden tener asignaciones (de momento).

En cambio los metodos anonimos no pueden transformarse en arboles de expresiones. Lo siguiente tampoco compila:

Citar
Func<int> func = () => 10;
Func<int> func2 = delegate() { return 10; };
Expression<Func<int>> exp3 = delegate() { return 10; }; // Error

Hay mas detalles que dejan ver que este tipo de cosas se manejan de manera un poco excepcional por el compilador, por ejemplo esto no compila porque directamente no esta permitido:

Citar
var var = () => 10;
var var2 = delegate() { return 10; };

Pero bueno, en general son casos un poco raros...






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.