2010-10-27 46 views
1

我在寫一個位圖編輯器,我使用命令模式來表示將轉換文檔的操作。我將目前爲止執行的所有命令保存在列表中,爲了實現撤消,我將文檔恢復到其初始狀態,然後重播除最後一個命令以外的所有命令。實現強大的持久撤消/重做功能

我想我的撤銷/重做系統具有以下功能:當用戶關閉編輯器並返回,文檔,包括可用的撤消和重做命令,應恢復到時它所處的狀態用戶離開了。

我正在實施這個Android應用程序可以在它從內存中清除之前給予很少的注意,例如,用戶接到電話。另外,我的一些命令是例如所有這些用戶繪製的x,y合作伙伴的列表,因此這些可能需要一些時間來保存到磁盤。

我現在的想法是:

  1. 當執行一個新的動作,命令對象添加到列表■對於需要保存到磁盤的命令。
  2. 使用後臺線程將不斷地從列表S獲取命令並將其保存到磁盤。所用文件名的後綴將按順序編號。例如,如果用戶填充了屏幕,然後畫了2個圓圈,則命令文件可能被稱爲FillCommand1.cmd,DrawCircleCommand2.cmd,DrawCircleCommand3.cmd。
  3. 我們定期保存一個「檢查點」命令,其目的是存儲完整的文檔狀態,以便即使其中一個.cmd文件被損壞,我們也可以恢復最近的文檔版本。
  4. 當用戶退出應用程序時,後臺線程會嘗試完成保存所有可能的命令(但可能會被終止)。
  5. 在啓動時,我們尋找最近的.cmd文件,它代表我們可以成功加載的檢查點。在這之後我們可以加載的所有.cmd文件(即某些文件可能已損壞)進入重做命令列表,我們可以加載第一個檢查點和我們可以加載的最舊檢查點之間加載的所有.cmd文件進入撤消列表。

我想撤消限制爲大約20或30的命令回來,所以我需要額外的邏輯丟棄命令,刪除.cmd文件的,我得擔心多線程行爲。這個系統看起來很複雜,需要大量的測試來確保它不會出錯。

Java或Android中有什麼可以幫助簡化這一點嗎?我是否在任何地方重塑車輪?也許數據庫會更好?

+0

「這個系統看起來很複雜,需要進行大量的測試以確保它不會出錯。」歡迎來到現實世界中的應用程序。一個函數式編程風格可能會幫助(留下舊值 - 不解決應用程序退出/持久性問題),但是你可能會遇到內存使用問題。 「...一個數據庫......」這可能有助於速度,但它不會從根本上讓它變得更容易,我不相信。除非你有一個內置歷史的類似git的數據庫。 – 2010-10-27 23:54:11

+0

大多數位圖編輯器操作具有破壞性,所以函數式編程風格方法對我看不出太多幫助。 – RichardNewton 2010-10-28 00:26:37

+0

沒錯。但是如果你的代碼是返回NewBitmap的Execute(Bitmap,Action),那麼你將擁有你的狀態。當然這會強制複製你可能不想要的位圖。僅僅因爲典型的方法是破壞性的並不意味着沒有其他的方法(儘管在大多數情況下,用手工抄錄破壞性可能更好)。你選擇存儲狀態的方法可能就是你想要的。 – 2010-10-28 18:34:44

回答

0

而不是恢復到原來的然後執行所有操作,考慮使命令可逆。這樣,如果您決定增加撤消歷史記錄的大小,則不會在撤消時引入延遲潛力。另外,正如Jared Updike指出的那樣,您的應用程序可能會從過去和未來緩存渲染結果中受益。

我認爲你使用基於文件系統的解決方案過於複雜。如果您想要保留當前工作文檔的整個歷史記錄的備份,則應該僅以附加模式保持未緩衝的日誌處於打開狀態,並將操作記錄到該日誌中。日誌應該與正在編輯的應用程序和文件的特定實例相關聯,因此您不必擔心另一個線程踩在腳趾上。從該日誌加載應該與從普通保存文件加載非常相似。只要您遇到撤銷操作,就丟棄最後一次讀取操作。

+0

「考慮讓命令可逆。」對於位圖編輯器,這意味着存儲例如矩形的位圖,畫筆畫在上面。如果用戶快速繪製並且沒有足夠的內存來保存RAM中的位圖,直到將它們寫入磁盤,我無法將這些文件快速保存到磁盤。「你應該保持一個無緩衝的日誌打開在追加模式,並記錄行動。」謝謝,這看起來簡單得多。我是否不必擔心文件被損壞,但如果我的應用程序在我可以乾淨地關閉日誌之前被殺死? – RichardNewton 2010-10-28 00:31:27

+0

@RichardNewton:這只是一個想法。無論如何,如果所有日誌需要關閉,您不需要執行儘可能多的關閉操作,並且如果緩衝區在您寫入句柄時被刷新,我無法想象即使應用程序沒有出現什麼問題乾淨地出口。如果一個現代操作系統沒有關閉進程已經死掉的文件,這確實是一件壞事! – 2010-10-28 00:56:26

0

那麼,你的代碼本質上可能是必要的,其中應用程序的狀態由用戶的操作進行修改。這可能是快速和直接的。撤銷基本上是時間旅行,如果您通過修改狀態來打破舊狀態,您將不得不存儲配方來重新計算它,或者可以重新計算它的歷史記錄。

就像你說的那樣,你可以存儲動作和初始狀態並向前播放它們(停在用戶選擇的歷史新點上),但這意味着撤消一個動作可能導致n個動作重放。一種方法是將保存的狀態副本存儲在歷史列表中,以便立即跳轉到給定狀態。爲避免使用太多的RAM /存儲,如果系統很聰明,它可以檢測歷史記錄中最近(非空)的已保存狀態,並向前重新計算這些少量狀態(假設您有所需的所有操作 - 假設操作是小和狀態很大(r)),直到達到正確的狀態。通過這種方式,您可以開始消除舊的已保存狀態(刪除或設置爲空)(根據成本函數丟棄狀態,與狀態返回的時間呈反向線性關係),快速撤銷最近的過去,以及內存/存儲高效的古代歷史。我已經用這種方法取得了成功。

+0

我實際上使用這種方法與我提到的檢查點。這是相當複雜的實施和它的工作。然而,在命令列表中只有一個緩慢的操作,例如一個非常大的繪畫命令,可能會導致撤消的滯後。我想在耗費時間的命令後我可以帶上一個檢查點。有關如何以強大的方式將命令存儲到磁盤的任何意見? – RichardNewton 2010-10-28 00:36:14

+0

你可以多說一點你緩慢的油漆命令嗎?這是基於刷子還是某種顏料桶/填充算法?你是對的,強調所有這些都是假設個體行爲是相當快的操作,因此可以重播,至少其中一些是重放的,從用戶的角度來看沒有太多延遲。 – 2010-10-28 17:59:39

+0

繪畫動作存儲爲x/y/size大小的列表。執行涉及使用給定的x/y/size大小繪製畫筆位圖。爲了使它看起來足夠像油漆一樣,你需要在繩索之間有一個小間距。無論如何,如果用戶決定在屏幕的大部分時間塗鴉幾秒鐘,播放命令可能需要大約0.5 - 1秒。如果你有這樣的一些命令,撤消需要一秒或更多時間,這會給用戶帶來不好的體驗。爲了解決這個問題,我會定期保存知道如何恢復整個文檔的檢查點,但這會使事情變得非常複雜。 – RichardNewton 2010-10-28 19:20:53