Undo Engine for Any Project

Started by Hardcoal, June 17, 2024, 02:52:18

Previous topic - Next topic

Hardcoal

Strict

Global UndoEng:UndoEngine_HC_Type = New UndoEngine_HC_Type 'Does not need Play Process

Graphics 800, 600

Global CrntSentence:String

Local Words:String[]
Words = Words +["Moshe"]
Words = Words +["Super"]
Words = Words +["Is"]
Words = Words +["List"]
Words = Words +["Melon"]
Words = Words +["Sheep"]

Repeat
  Cls

   'User Input
 
   'Add Random Word to Sentence
  If KeyHit(KEY_SPACE) Then
PreviousNumber_Type.AddUndo(CrntSentence)
CrntSentence = CrntSentence + " " + Words[Rnd(0, Words.Length - 1)]
End If

   'Undo
If KeyHit(KEY_Z) Then UndoEng.DoUndo()
 
   'Draw Sentence
    SetColor(0, 240, 230)
DrawText("Current Sentence:   " + CrntSentence, 100, 100)

   'Remarks
  
   SetColor(255, 0, 0)
      DrawText("Press Space To Generate Word", 10, 240)
DrawText("Press Z To Do Undo", 10, 260)

   'Debug

   SetColor(255, 240, 0)
  DrawText("Under Buffer: " + UndoEng.UndoList.count(), 10, 300)

Delay(5)
  Flip
Until KeyHit(KEY_ESCAPE)

'-------------- The Engine ---------------'

Type UndoEngine_HC_Type 'undo manager

Field UndoList:TList = CreateList()

Field LocationInUndo   'For redo Not Used Yet
    Field UndoLimit = 100  'Not Used

   'Add
  Method AddUndo(UndoType:UndoModel_Extender)  'Must Add Engine if Its not based on Default Engine. Example: RememberMelody_Undo.AddUndo(MelodyCont.CurrentMelody.Undo)
ListAddLast(UndoList, UndoType)
End Method

   'Use
Method DoUndo()

If UndoList.count() = 0 Then Return

Local CurrentUndo:UndoModel_Extender = UndoModel_Extender(UndoList.last())
CurrentUndo.DoUndoPrivate()

ListRemove(UndoList, UndoList.last())

CurrentUndo = Null
End Method

Method Redo() 'Not Used
End Method

   'Debug
  
   Method DisplayUndoDetails_Process()
Print UndoList.count()
End Method

End Type

Type UndoModel_Extender

Field UndoName:String

'Field ActionType:Type_Class
Field UndoEng:UndoEngine_HC_Type  'Parent

   'Abstract

Method DoUndoPrivate() Abstract

End Type

'----------------------

Type PreviousNumber_Type Extends UndoModel_Extender

Field SavedSentence:String

   'Direct Use
Function AddUndo:UndoModel_Extender(Sentence:String)
Local NU:PreviousNumber_Type = New PreviousNumber_Type   'Dont Delete
NU.UndoName = TTypeId.ForObject(NU).name() 'Dont Delete

   '---Your Undo Code---'
  
   NU.SavedSentence = Sentence

UndoEng.AddUndo(NU) 'Dont Delete
Return NU 'Dont Delete
End Function

   'Is Called By UndoEng [Not For Direct Use]
Method DoUndoPrivate()
CrntSentence = SavedSentence
End Method

End Type


Ive added an example..

If anyone has some remarks or ideas.. or corrections.. 
Please share



Baggey

I have never seen BlitzMaxNG Code like this  NU.UndoName = TTypeId.ForObject(NU).name() :-[ 

Thanks for sharing. I have a use for this concept already ;)

Kind Regards Baggey

Running a PC that just Aint fast enough!? i7 4Ghz Quad core 24GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Hardcoal

I did amazing things using this method that are not possible other way on my editor.
But sure, I'm glad I renewed someone something :)

Oh BTW I'm using blitzmax Vanilla, but im sure it works on NG as well

Derron

#3
Method DoUndo()

If UndoList.count() = 0 Then Return

Local CurrentUndo:UndoModel_Extender = UndoModel_Extender(UndoList.last())
CurrentUndo.DoUndoPrivate()

ListRemove(UndoList, UndoList.last())

CurrentUndo = Null
End Method

I would consider "last()" and "count()" to be kind of "equal" performance wise (count() recalculates if not done but else returns a cached value, last() returns some property too after a check was done).

so maybe simply do


Method DoUndo()
Local CurrentUndo:UndoModel_Extender = UndoModel_Extender(UndoList.last())
If CurrentUndo
CurrentUndo.DoUndoPrivate()
UndoList.RemoveLast()
EndIf
End Method

What it changes:
- it only removes from the undo list when there was some valid object added (your code always deletes the last one, no matter of what object type it was ... not a problem as you most probably only add compatible stuff :D)
- avoids scanning the whole list to remove the object
- avoids removing "the first hit" (in this case you won't have an entry twice in the list, but if you had, it would remove the first occourence instead of the last one)


Generally:
I would implement an undo system with "actions". A TAction would have "do" and "undo" (or however you call it) and eg a keyhit would create an "TKeyHitAction extends TAction" and so on. So similar to your "UndoModel_extender".
For an Editor it would be "TEditorAction extends TAction" or similar. You will recognize that this becomes rather "similar" looking to how "TEvent" and descendants would be implemented ... so yeah, you could even base them on events ("something happened")


With NG you could even use "interfaces" so any TAction could by default not "undo", but it can implement an Interface ("IRevertableAction" or "IUndoableAction) or so which then requires specific methods to exist ...

Why TAction? Because you should/could use them in your games too. "TBuyAction extends TAction" ... "TCommandUnitAction extends TAction" ...
If you stored "TActions" in a log, you could "replay" games if everything else runs deterministic (means there is no true "randomness" in it but everything on pseudo randomness with "same seed + same count of calls = same results").
It also would allow to "script" things like tutorials ("click on the unit and send it to the mine to harvest").


So you see, undo/redo in a text editor and interaction in games ... can base on the same "approach/idea". This will be even more "visible" if you think of "card games" or "mahjongg", "puzzle games" etc (so where the interaction happens in "steps" too).
 

PS: A synonym for "TAction" could be "TCommand" (google for ... command pattern) which also fits ... TChangeDocumentAction/TChangeDocumentCommand (any change to the document ... is it copy, paste or based on simply keyhits ... it is all the same thing: it alters the document's content, so store "document position", "change value" and the "change type" (cut/remove, paste/add)--- or if you distinguish between the actions the type itself could be used to see if you add or remove your "change value" at/from the given position).

bye
Ron