Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

undo & redo support #4863

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
510cc0e
add feature flag 'undo'
emptyrivers Feb 5, 2024
e515eda
add some toolbar buttons
emptyrivers Feb 5, 2024
1e68a9a
make a debugprint
emptyrivers Feb 5, 2024
282ab6b
simple time machine
emptyrivers Feb 5, 2024
9ec2aa5
generalize the time machine to any kind of mutation
emptyrivers Feb 6, 2024
4036517
force an ordering of time machine records
emptyrivers Feb 7, 2024
57b66b4
further tweaks to TimeMachine
emptyrivers Feb 7, 2024
5653f19
lets just crib database language fully and make the concept of a tran…
emptyrivers Feb 7, 2024
00f062b
support undoing changes to action options
emptyrivers Feb 7, 2024
1664ffd
support undo for many options in group & display tab
emptyrivers Feb 8, 2024
7389eff
finish supporting information options
emptyrivers Feb 8, 2024
f126cbe
add type hint
emptyrivers Feb 8, 2024
d365d55
dry run of animation options
emptyrivers Feb 8, 2024
cfe67c0
fix a few bugs
emptyrivers Feb 8, 2024
fb06b65
wip
emptyrivers Feb 10, 2024
e8f51e9
drop some unnecessary set funcs
emptyrivers Mar 5, 2024
85b87fd
fix a couple bugs
emptyrivers Mar 5, 2024
b907f4d
convert *most* of custom options
emptyrivers Mar 5, 2024
047ad03
blablabla
emptyrivers Mar 24, 2024
77ee2c2
undoify BT2
emptyrivers Mar 25, 2024
cf7b335
have some more
emptyrivers Mar 26, 2024
1bb1eb8
drop conditionvariable variable
emptyrivers Mar 28, 2024
18b8eda
do a bit of optional chaining
emptyrivers Mar 29, 2024
9d25a9d
undoif conditions
emptyrivers Mar 29, 2024
b6bf29c
revert
emptyrivers Mar 29, 2024
442e767
fixes
emptyrivers Mar 29, 2024
e6ebcc7
.
emptyrivers Mar 29, 2024
b692a6b
make it sane!
emptyrivers Mar 29, 2024
a30700f
missed an Add()
emptyrivers Mar 29, 2024
e06b9ed
undoify add subretion button
emptyrivers Mar 29, 2024
87d351b
drop check2 option
emptyrivers Mar 29, 2024
6c76fce
drop more duplicates
emptyrivers Mar 30, 2024
e9b01bc
some generic trigger stuff
emptyrivers Apr 3, 2024
29eff53
undoify constructoptions
emptyrivers Apr 3, 2024
a09586d
missed trigger.event
emptyrivers Apr 3, 2024
9d258e4
allow for nil index on insert/remove actions
emptyrivers Apr 3, 2024
aa5c0cf
undoify some trigger options
emptyrivers Apr 3, 2024
8d73e1d
undoify trigger delete
emptyrivers Apr 3, 2024
d3a5bb3
fix mistakes
emptyrivers Apr 4, 2024
0c64a06
fix the worst formatted code in the entire codebase :p
emptyrivers Apr 4, 2024
1b48c11
undoify aurabar
emptyrivers Apr 4, 2024
3318d51
undoify dynamic groups
emptyrivers Apr 4, 2024
047350b
improve time machine annotation
emptyrivers Apr 4, 2024
56e39a4
undoify Group options
emptyrivers May 1, 2024
4cedc32
squelch global access lint for WeakAuras global
emptyrivers May 1, 2024
a9a4f31
add a setmany
emptyrivers May 1, 2024
6a974d9
drop spurious adds
emptyrivers May 9, 2024
3e1b5df
fix
emptyrivers Jan 14, 2025
16d08b2
re-add line from bad rebase
emptyrivers Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .luarc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"invisible"
],
"diagnostics.globals": [
"WeakAuras",
"AbbreviateLargeNumbers",
"ACCEPT",
"AceGUIWeakAurasMultiLineEditBoxInsertLink",
Expand Down
10 changes: 10 additions & 0 deletions WeakAuras/Features.lua
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,13 @@ Features:Register({
id = "debug",
autoEnable = {"dev"}
})

Private.DebugPrint = Features:Wrap("debug", function(...)
print(...)
end)

Features:Register({
id = "undo",
autoEnable = {"dev", "pr"},
persist = true
})
1 change: 1 addition & 0 deletions WeakAuras/Init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ Private.frames = {}
--- @field subRegionOptions table<string, table>
--- @field subRegionTypes table<string, table>
--- @field tick_placement_modes table<string, string>
--- @field TimeMachine TimeMachine
--- @field tinySecondFormat fun(value: string|number): string?
--- @field TraverseAll fun(data: auraData): traverseFunction, auraData
--- @field TraverseAllChildren fun(data: auraData): traverseFunction, auraData
Expand Down
299 changes: 299 additions & 0 deletions WeakAuras/TimeMachine.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
---@type string
local AddonName = ...
---@class Private
local Private = select(2, ...)

---@class TimeMachine
local TimeMachine = {
---@type change
next = {
forward = {},
backward = {}
},
transaction = false,
---@type change[]
changes = {},
---@type table<actionType, Action>
actions = {},
---@type table<actionType, Inverter>
inverters = {},
index = 0
}

Private.TimeMachine = TimeMachine

---@alias key string | number
---@alias keyPath key | (key)[]
---@alias Action<T> fun(data: table, path: keyPath, payload: T)
---@alias Inverter<T, S> fun(data: table, path: keyPath, payload?: T): actionType, keyPath, S
---@alias actionType string

---@class actionRecord
---@field uid uid
---@field actionType actionType
---@field path keyPath
---@field payload any
---@field effect? sideEffect | false

---@class sideEffect
---@field tag string
---@field func function


---@class change
---@field forward actionRecord[]
---@field backward actionRecord[]

---@param data table
---@param path keyPath
---@return table, key
local function resolveKey(data, path)
if type(path) ~= 'table' then
return data, path
end
local tbl = data
local i = 1
while i < #path do
if tbl[path[i]] == nil then
tbl[path[i]] = {}
elseif type(tbl[path[i]]) ~= 'table' then
error("Path is not valid: " .. table.concat(path, '.') .. " at " .. path[i])
end
tbl = tbl[path[i]]
i = i + 1
end
return tbl, path[#path]
end

local function copy(tbl, key)
if type(tbl[key]) == "table" then
return CopyTable(tbl[key])
else
return tbl[key]
end
end

---@type Action<nil>
function TimeMachine.actions.none(data, path)
end

---@type Inverter<nil, nil>
function TimeMachine.inverters.none(data, path)
return 'none', path, nil
end

---@type Action<any>
function TimeMachine.actions.set(data, path, value)
local tbl, key = resolveKey(data, path)
tbl[key] = value
end

---@type Inverter<any, any>
function TimeMachine.inverters.set(data, path)
local tbl, key = resolveKey(data, path)
return 'set', path, copy(tbl, key)
end

---@type Action<table<string, any>>
function TimeMachine.actions.setmany(data, path, values)
local tbl, key = resolveKey(data, path)
for k, v in pairs(values) do
tbl[key][k] = v
end
end

---@type Inverter<table<string, any>, table<string, any>>
function TimeMachine.inverters.setmany(data, path, values)
local tbl, key = resolveKey(data, path)
local inverse = {}
for k, v in pairs(values) do
inverse[k] = copy(tbl[key], k)
end
return 'setmany', path, inverse
end

---@type Action<{index: number?, value: any}>
function TimeMachine.actions.insert(data, path, payload)
local tbl, key = resolveKey(data, path)
if payload.index == nil then
table.insert(tbl[key], payload.value)
else
table.insert(tbl[key], payload.index, payload.value)
end
end

---@type Inverter<{index: number, value: any}, number>
function TimeMachine.inverters.insert(data, path, payload)
return 'remove', path, payload.index
end

---@type Action<number | nil>
function TimeMachine.actions.remove(data, path, payload)
local tbl, key = resolveKey(data, path)
if payload == nil then
table.remove(tbl[key])
else
table.remove(tbl[key], payload)
end
end

---@type Inverter<number, {index: number?, value: any}>
function TimeMachine.inverters.remove(data, path, payload)
local tbl, key = resolveKey(data, path)
return 'insert', path, {index = payload, value = copy(tbl[key], payload or #tbl[key])}
end

---@type Action<{[1]: number, [2]: number}>
function TimeMachine.actions.swap(data, path, payload)
local tbl, key = resolveKey(data, path)
tbl[key][payload[1]], tbl[key][payload[2]] = tbl[key][payload[2]], tbl[key][payload[1]]
end

---@type Inverter<{[1]: number, [2]: number}, {[1]: number, [2]: number}>
function TimeMachine.inverters.swap(data, path, payload)
return 'swap', path, {payload[2], payload[1]}
end

---@type Action<{[1]: number, [2]: number}>
function TimeMachine.actions.move(data, path, payload)
local tbl, key = resolveKey(data, path)
local value = table.remove(tbl, payload[1])
table.insert(tbl[key], payload[2], value)
end

---@type Inverter<{[1]: number, [2]: number}, {[1]: number, [2]: number}>
function TimeMachine.inverters.move(data, path, payload)
return 'move', path, {payload[2], payload[1]}
end

---@param path keyPath
local function keyPathToString(path)
if type(path) == 'table' then
return table.concat(path, '.')
else
return path
end
end

function TimeMachine:StartTransaction()
self:Reject()
self.transaction = true
end

---@param record actionRecord
function TimeMachine:Append(record)
local action = self.actions[record.actionType]
Private.DebugPrint("Forward action", record.actionType, "for", record.uid, "at", keyPathToString(record.path), "with", record.payload)
if not action then
error("No action for actionType: " .. record.actionType)
end
local inverter = self.inverters[record.actionType]
if not inverter then
error("No inverter for action: " .. record.actionType)
end
local actionType, path, payload = inverter(Private.GetDataByUID(record.uid), record.path, record.payload)
local inverseRecord = {
uid = record.uid,
actionType = actionType,
path = path,
payload = payload
}
Private.DebugPrint("Backward action", actionType, "for", record.uid, "at", keyPathToString(path), "with", payload)
table.insert(self.next.forward, record)
table.insert(self.next.backward, inverseRecord)
if not self.transaction then
self:Commit(true)
end
end

---@param records actionRecord[]
function TimeMachine:AppendMany(records)
local commit = false
if not self.transaction then
self:StartTransaction()
commit = true
end
for _, record in ipairs(records) do
self:Append(record)
end
if commit then
self:Commit()
end
end

function TimeMachine:Reject()
self.next = {
forward = {},
backward = {}
}
self.transaction = false
end

---@param instant? boolean
function TimeMachine:Commit(instant)
if not self.transaction and not instant then return end
while self.index < #self.changes do
table.remove(self.changes)
end
table.insert(self.changes, self.next)
self.next = {
forward = {},
backward = {}
}
self.transaction = false
return self:StepForward()
end

---@param records actionRecord[]
---@param skipEffects? boolean
function TimeMachine:Apply(records, skipEffects)
local effects = {}
for _, record in ipairs(records) do
local action = self.actions[record.actionType]
if not action then
error("No action for actionType: " .. record.actionType)
end
local data = Private.GetDataByUID(record.uid)
action(data, record.path, record.payload)
if record.effect ~= false then
effects[record.uid] = effects[record.uid] or {}
if record.effect then
effects[record.uid][record.effect.tag] = record.effect.func
end
end
end
if not skipEffects then
for uid, effect in pairs(effects) do
local data = Private.GetDataByUID(uid)
for _, func in pairs(effect) do
func(data)
end
WeakAuras.Add(data)
end
end
end

function TimeMachine:StepForward()
if self.index < #self.changes then
self.index = self.index + 1
self:Apply(self.changes[self.index].forward)
end
end

function TimeMachine:StepBackward()
if self.index > 0 then
self:Apply(self.changes[self.index].backward)
self.index = self.index - 1
end
end

function TimeMachine:TravelTo(index)
if index < 0 or index > #self.changes then
error("Invalid index: " .. index)
end
local direction = index > self.index and "forward" or "backward"
for i = self.index, index, direction == "forward" and 1 or -1 do
self:Apply(self.changes[i][direction], i ~= index)
end
self.index = index
end
1 change: 1 addition & 0 deletions WeakAuras/WeakAuras.toc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Modernize.lua
Animations.lua
Conditions.lua
AnchorToWeakAuras.lua
TimeMachine.lua

# Trigger systems
LibSpecializationWrapper.lua
Expand Down
2 changes: 1 addition & 1 deletion WeakAuras/WeakAuras_Vanilla.toc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Modernize.lua
Animations.lua
Conditions.lua
AnchorToWeakAuras.lua

TimeMachine.lua
# Trigger systems
BuffTrigger2.lua
GenericTrigger.lua
Expand Down
Loading
Loading