summaryrefslogtreecommitdiff
path: root/src/undo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/undo.cpp')
-rw-r--r--src/undo.cpp309
1 files changed, 309 insertions, 0 deletions
diff --git a/src/undo.cpp b/src/undo.cpp
new file mode 100644
index 0000000..d55cedc
--- /dev/null
+++ b/src/undo.cpp
@@ -0,0 +1,309 @@
+struct history_info {
+ uint16 ActionCount_Total;
+ uint16 ActionCount_EndOfPlayhead;
+ uint16 ActionCount_Current;
+
+ uint64 ActionOffset_Total;
+ uint64 ActionOffset_EndOfPlayhead;
+ uint64 ActionOffset_Current;
+
+ uint64 EntrySize_Current;
+};
+
+static uint64
+History_GetActionSize(history_entry_list *History, int i, uint16 ActionCount_EndOfPlayhead)
+{
+ history_action *Action = &History->Action[i];
+ if (Action->Type == action_type_swap || Action->Type == action_type_swap_bitmap)
+ return Action->Size;
+ return 0;
+}
+
+// Returns information on the undo tree based on three points of data: the
+// total amount of entries, the location of the playhead, and whatever location
+// SampleIndex is set to, which should be either one minus playhead or the playhead.
+
+// I wrote this with the intent of precomputing things like the tree's total
+// size and the size of the current action, because calculating them in the
+// Action_Redo/Undo calls got extremely confusing.
+
+static history_info
+History_GetTreeInfo(history_entry_list *History, uint16 SampleIndex)
+{
+ history_info Info = {};
+
+ for (int i = 0; i < History->NumberOfEntries; i++)
+ {
+ history_entry *Entry = &History->Entry[i];
+ Info.ActionCount_Total += Entry->NumberOfActions;
+ if (i < History->EntryPlayhead)
+ Info.ActionCount_EndOfPlayhead += Entry->NumberOfActions;
+ if (i < SampleIndex)
+ Info.ActionCount_Current += Entry->NumberOfActions;
+ }
+
+ for (int i = 0; i < Info.ActionCount_Total; i++)
+ {
+ uint64 Size = History_GetActionSize(History, i, Info.ActionCount_EndOfPlayhead);
+ Info.ActionOffset_Total += Size;
+ if (i < Info.ActionCount_EndOfPlayhead)
+ Info.ActionOffset_EndOfPlayhead += Size;
+
+ if (i < Info.ActionCount_Current)
+ Info.ActionOffset_Current += Size;
+ else if (i < Info.ActionCount_EndOfPlayhead) // Only increment the current size if i is between
+ Info.EntrySize_Current += Size; // the current count and playhead count!
+ }
+
+ return Info;
+}
+
+// This function works exactly the same whether we're in undo/redo, pretty neat.
+void History_Action_DoSwapBitmap(memory *Memory, history_action *ActionPointer, uint8 *Address_HistoryTree_Start, uint8 *Address_HistoryTree_End, uint8 *Address_Data)
+{
+ history_action Action = *ActionPointer;
+
+ uint64 FullSize = Action.ShiftAmount;
+ uint64 CompressedSize = Action.Size;
+
+ uint32 BytesForCompressedPart = (uint32)100 * 1024 * 1024;
+ void *UncompressedTempBuffer = Memory_PushScratch(Memory, FullSize);
+ void *CompressedTempBuffer = Memory_PushScratch(Memory, BytesForCompressedPart);
+
+ // First we decompress and swap the saved pixels with the newer ones...
+ Data_Decompress(Memory, Address_HistoryTree_Start, CompressedSize, UncompressedTempBuffer, FullSize);
+ Bitmap_SwapData((uint8 *)UncompressedTempBuffer, (uint8 *)Address_Data, FullSize, Action.Direction);
+
+ // Then we recompress the old pixels and evaluate its size, and since the size can be different...
+ uint64 NewSize = Data_Compress(Memory, UncompressedTempBuffer, FullSize, CompressedTempBuffer, BytesForCompressedPart, 0); // Z_BEST_SPEED);
+
+ // we have to shift the undo stack.
+ if (NewSize != CompressedSize) {
+ int16 ShiftDirection = (NewSize > CompressedSize) ? 1 : -1;
+ uint64 ShiftAmount = (NewSize > CompressedSize) ? NewSize - CompressedSize : CompressedSize - NewSize;
+ Arbitrary_ShiftData(Address_HistoryTree_Start + CompressedSize, Address_HistoryTree_End, ShiftAmount, ShiftDirection);
+ }
+ Memory_Copy(Address_HistoryTree_Start, (uint8 *)CompressedTempBuffer, NewSize);
+
+ ActionPointer->Size = NewSize;
+ Memory_PopScratch(Memory, BytesForCompressedPart);
+ Memory_PopScratch(Memory, FullSize);
+}
+
+void History_Action_Undo(memory *Memory, history_info Info, history_action *ActionPointer, uint64 Action_Offset)
+{
+ history_entry_list *History = &Memory->History;
+
+ // Only swap_bitmap should touch the data!
+ history_action Action = *ActionPointer;
+
+ uint8 *Address_HistoryTree_Start = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Action_Offset);
+ uint8 *Address_HistoryTree_End = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total);
+ uint8 *Address_Data = (uint8 *)Memory_AddressAtOffset(Memory, Action.TableName, Action.ByteOffset);
+
+ if (Action.Type == action_type_swap) {
+ Arbitrary_SwapData(Memory, Address_HistoryTree_Start, Address_Data, Action.Size);
+ } else if (Action.Type == action_type_swap_bitmap) {
+ History_Action_DoSwapBitmap(Memory, ActionPointer, Address_HistoryTree_Start, Address_HistoryTree_End, Address_Data);
+ } else if (Action.Type == action_type_shift) {
+ // In order to shift back we have to start the address at where the
+ // shifted chunk is _now_, not when we called the function.
+ uint64 NewByteOffset = (Action.Direction > 0) ? Action.ByteOffset + Action.ShiftAmount : Action.ByteOffset - Action.ShiftAmount;
+ void *Address_Start = Memory_AddressAtOffset(Memory, Action.TableName, NewByteOffset);
+ uint8 *Address_End = (uint8 *)Address_Start + Action.Size;
+ // Direction also needs to be reversed.
+ int16 Direction = Action.Direction * -1;
+ Arbitrary_ShiftData((uint8 *)Address_Start, Address_End, Action.ShiftAmount, Direction);
+ } else {
+ Assert(0);
+ }
+}
+
+void History_Action_Redo(memory *Memory, history_info Info, history_action *ActionPointer, uint64 Action_Offset) {
+
+ history_entry_list *History = &Memory->History;
+
+ history_action Action = *ActionPointer;
+
+ uint8 *Address_HistoryTree_Start = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Action_Offset);
+ uint8 *Address_HistoryTree_End = (uint8 *)Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total);
+ uint8 *Address_Data = (uint8 *)Memory_AddressAtOffset(Memory, Action.TableName, Action.ByteOffset);
+
+ if (Action.Type == action_type_swap) {
+ Arbitrary_SwapData(Memory, Address_HistoryTree_Start, Address_Data, Action.Size);
+ } else if (Action.Type == action_type_swap_bitmap) {
+ History_Action_DoSwapBitmap(Memory, ActionPointer, Address_HistoryTree_Start, Address_HistoryTree_End, Address_Data);
+ } else if (Action.Type == action_type_shift) {
+ void *Address_Start = Memory_AddressAtOffset(Memory, Action.TableName, Action.ByteOffset);
+ uint8 *Address_End = (uint8 *)Address_Start + Action.Size;
+ int16 Direction = Action.Direction;
+ Arbitrary_ShiftData((uint8 *)Address_Start, Address_End, Action.ShiftAmount, Direction);
+ } else {
+ Assert(0);
+ }
+}
+
+void History_Undo(memory *Memory) {
+ history_entry_list *History = &Memory->History;
+ if (History->EntryPlayhead == 0) return;
+
+ // We want Current to represent the beginning of the current entry, so we
+ // subtract one from the playhead.
+ history_info Info = History_GetTreeInfo(History, History->EntryPlayhead - 1);
+ history_entry *Entry = &History->Entry[History->EntryPlayhead - 1];
+
+ // This puts us at the end of the current entry's offset.
+ uint64 ActionOffset_Stepback = Info.ActionOffset_Current + Info.EntrySize_Current;
+
+ for (int i = Entry->NumberOfActions - 1; i >= 0; i--) {
+ history_action *Action = &History->Action[Info.ActionCount_Current + i];
+
+ // We step backwards only if the action is currently storing data.
+ if (Action->Type != action_type_shift)
+ ActionOffset_Stepback -= Action->Size;
+
+ History_Action_Undo(Memory, Info, Action, ActionOffset_Stepback);
+ }
+
+ History->EntryPlayhead--;
+
+}
+
+void History_Redo(memory *Memory) {
+ history_entry_list *History = &Memory->History;
+ if (History->EntryPlayhead == History->NumberOfEntries) return;
+
+ // NOTE(fox): The third part of this function for recording the "current"
+ // action's size is only necessary for the undo call, so it's not correct on the redo.
+ history_info Info = History_GetTreeInfo(History, History->EntryPlayhead);
+ history_entry *Entry = &History->Entry[History->EntryPlayhead];
+
+ History->EntryPlayhead++;
+
+ uint64 ActionOffset_Stepforward = Info.ActionOffset_Current;
+
+ for (int i = 0; i < Entry->NumberOfActions; i++) {
+ history_action *Action = &History->Action[Info.ActionCount_Current + i];
+
+ History_Action_Redo(Memory, Info, Action, ActionOffset_Stepforward);
+
+ // We step forwards only if the action is currently storing data.
+ if (Action->Type != action_type_shift)
+ ActionOffset_Stepforward += Action->Size;
+
+ }
+
+}
+
+// This is only called when we're certain the action is going to be taken.
+void History_Entry_Commit(memory *Memory, char *Name)
+{
+ history_entry_list *History = &Memory->History;
+ history_entry *Entry = &History->Entry[History->EntryPlayhead];
+ Entry->Name = Name;
+ Entry->NumberOfActions = 0;
+ // Effectively deletes entries in front if we're beginning out of an undo.
+ if (History->NumberOfEntries != History->EntryPlayhead)
+ History->NumberOfEntries = History->EntryPlayhead;
+ History->NumberOfEntries++;
+ History->EntryPlayhead++;
+ Memory->IsFileSaved = false;
+ Memory->PurgeCache = true;
+#if DEBUG
+ Assert(Debug.UndoState != 1); // You forgot to end a History_Entry_Commit()!
+ Debug.UndoState = 1;
+#endif
+}
+
+void History_Entry_End(memory *Memory)
+{
+ history_entry_list *History = &Memory->History;
+#if DEBUG
+ Debug.UndoState = 0;
+#endif
+}
+
+// NOTE(fox): Shift is the only action that additionally changes the data after
+// the info is put on the tree, since it's always the same function used.
+
+static void History_Action_Add(memory *Memory, history_action ActionData, void *ReferenceData = NULL)
+{
+ history_entry_list *History = &Memory->History;
+ history_entry *Entry = &History->Entry[History->EntryPlayhead - 1];
+
+ history_info Info = History_GetTreeInfo(History, History->EntryPlayhead - 1);
+
+ history_action *Action = &History->Action[Info.ActionCount_Total];
+ *Action = ActionData;
+
+ if (Action->Type == action_type_swap)
+ {
+ void *Address_HistoryTree = Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total);
+ void *Address_Data = Memory_AddressAtOffset(Memory, Action->TableName, Action->ByteOffset);
+ Memory_Copy((uint8 *)Address_HistoryTree, (uint8 *)Address_Data, Action->Size);
+ } else if (Action->Type == action_type_swap_bitmap)
+ {
+ // The address of data to be stored in the undo tree is not the same as
+ // the address to be recorded, so we have to pass it manually.
+ void *Address_HistoryTree = Memory_AddressAtOffset(Memory, P_UndoBuffer, Info.ActionOffset_Total);
+ void *Address_Data = ReferenceData;
+
+ // Libz/miniz requires passing an amount of available space, and it
+ // returns how much space the compressed data actually is.
+ // We don't need to alloc any more memory since we can just use the
+ // history tree as the destination.
+ // ShiftAmount is also being used to save the uncompressed size.
+ uint32 BytesForCompressedPart = (uint32)100 * 1024 * 1024;
+ Action->Size = Data_Compress(Memory, Address_Data, Action->ShiftAmount, Address_HistoryTree, BytesForCompressedPart, 0); // Z_BEST_SPEED);
+
+ } else if (Action->Type == action_type_shift)
+ {
+ void *Address_Start = Memory_AddressAtOffset(Memory, Action->TableName, Action->ByteOffset);
+ uint8 *Address_End = (uint8 *)Address_Start + Action->Size;
+ Arbitrary_ShiftData((uint8 *)Address_Start, Address_End, Action->ShiftAmount, Action->Direction);
+ }
+
+ Entry->NumberOfActions++;
+}
+
+// Helper functions for different tables.
+
+static void History_Action_BitmapPaint(memory *Memory, uint64 Size_Uncompressed,
+ void *SourceBitmapAddress, void *CachedPaintAddress, int16 BytesPerPixel)
+{
+ void *Address_Start = Memory->Slot[F_PrincipalBitmaps].Address;
+ uint64 ByteOffset = (ptrsize)SourceBitmapAddress - (ptrsize)Address_Start;
+ history_action Action = { F_PrincipalBitmaps, action_type_swap_bitmap, 0, ByteOffset, Size_Uncompressed, BytesPerPixel };
+ History_Action_Add(Memory, Action, CachedPaintAddress);
+}
+
+static void History_Action_Shift(memory *Memory, memory_table_list TableName,
+ void *Address0, void *Address1, uint64 Amount, int16 Direction)
+{
+ void *Address_Start = Memory->Slot[TableName].Address;
+ uint64 ByteOffset = (ptrsize)Address0 - (ptrsize)Address_Start;
+ uint64 Size = (ptrsize)Address1 - (ptrsize)Address0;
+
+ history_action Action = { TableName, action_type_shift, Size, ByteOffset, Amount, Direction };
+
+ History_Action_Add(Memory, Action);
+}
+
+static void History_Action_Block_Swap(memory *Memory, memory_table_list TableName, void *Address_Data)
+{
+ void *Address_Start = Memory->Slot[TableName].Address;
+ uint64 Size = Memory->Slot[TableName].Block_ElementSize;
+ uint64 ByteOffset = (ptrsize)Address_Data - (ptrsize)Address_Start;
+ history_action Action = { TableName, action_type_swap, Size, ByteOffset, 0 };
+ History_Action_Add(Memory, Action);
+}
+
+// Remember to dereference pointers if taking the sizeof() a variable, or else Size will be 8!
+
+static void History_Action_Swap(memory *Memory, memory_table_list TableName, uint64 Size, void *Address_Data)
+{
+ void *Address_Start = Memory->Slot[TableName].Address;
+ uint64 ByteOffset = (ptrsize)Address_Data - (ptrsize)Address_Start;
+ history_action Action = { TableName, action_type_swap, Size, ByteOffset, 0 };
+ History_Action_Add(Memory, Action);
+}