Undo Engine for Any Project

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

Previous topic - Next topic

Hardcoal

Code: BASIC
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


Things I've done:   https://itch.io/profile/hardcoal  [I keep improving them btw]

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 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on 2 x HP Z24's . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Raspberry Pi Black Edition! , ZX Spectrum 48k, C64, 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
Things I've done:   https://itch.io/profile/hardcoal  [I keep improving them btw]

Derron

#3
Code: BASIC
		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


Code: BASIC
		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

Baggey

Going to be working on and adapting these ideas soon so i can create an editor which you type in, in basic or machine code. :-X

Then directly inject into SpecMax 128k Memory to run.

I already know the commands for the Zx Spectrum so i suppose that's the list of tokens. Then parse them with my text file.

I have no idea what im trying todo,  :))  So ill probably wing it and come up with something! :-[

Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on 2 x HP Z24's . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Raspberry Pi Black Edition! , ZX Spectrum 48k, C64, Enterprise 128K, The SID chip. Im Misunderstood!