mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
feat: mapper should not be redefined on every single item change
This commit is contained in:
parent
d0b7cff943
commit
e42c7fd8aa
2 changed files with 135 additions and 13 deletions
|
|
@ -24,6 +24,8 @@ local function mapped<K0, V0, K1, V1>(callback: Selector<{ [K0]: V0 }>, mapper:
|
||||||
local mappedAtom = atom({})
|
local mappedAtom = atom({})
|
||||||
local mappedAtomRef = setmetatable({ current = mappedAtom }, { __mode = "v" })
|
local mappedAtomRef = setmetatable({ current = mappedAtom }, { __mode = "v" })
|
||||||
local prevMappedItems: { [K1]: V1 } = {}
|
local prevMappedItems: { [K1]: V1 } = {}
|
||||||
|
local prevKeyForItem: { [K0]: K1 } = {}
|
||||||
|
local prevItems: { [K0]: V0 } = {}
|
||||||
local unsubscribe
|
local unsubscribe
|
||||||
|
|
||||||
local function listener(items: { [K0]: V0 })
|
local function listener(items: { [K0]: V0 })
|
||||||
|
|
@ -33,30 +35,103 @@ local function mapped<K0, V0, K1, V1>(callback: Selector<{ [K0]: V0 }>, mapper:
|
||||||
return unsubscribe()
|
return unsubscribe()
|
||||||
end
|
end
|
||||||
|
|
||||||
local mappedItems = table.clone(mappedAtom())
|
local hasChanges = false
|
||||||
local mappedKeys = {}
|
|
||||||
|
|
||||||
-- TODO: Only call mapper if the item has changed.
|
|
||||||
for key, item in next, items do
|
for key, item in next, items do
|
||||||
local newItem, newKey = mapper(item, key)
|
if prevItems[key] ~= item or prevKeyForItem[key] == nil then
|
||||||
if newKey == nil then
|
hasChanges = true
|
||||||
newKey = key :: any
|
break
|
||||||
end
|
end
|
||||||
if mappedItems[newKey :: K1] ~= newItem then
|
end
|
||||||
mappedItems[newKey :: K1] = newItem :: V1
|
if not hasChanges then
|
||||||
|
for key in next, prevItems do
|
||||||
|
if items[key] == nil then
|
||||||
|
hasChanges = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not hasChanges then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local currentMapped = mappedAtom()
|
||||||
|
local mappedItems: { [K1]: V1 } = currentMapped
|
||||||
|
local mappedKeys = {}
|
||||||
|
local newPrevKeyForItem: { [K0]: K1 } = {}
|
||||||
|
local updatedPrevItems: { [K0]: V0 } = {}
|
||||||
|
local changed = false
|
||||||
|
|
||||||
|
local function ensureWritable()
|
||||||
|
if mappedItems ~= currentMapped then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local nextItems: { [K1]: V1 } = {}
|
||||||
|
for key, value in next, currentMapped do
|
||||||
|
nextItems[key] = value
|
||||||
|
end
|
||||||
|
mappedItems = nextItems
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, item in next, items do
|
||||||
|
local mappedKey: K1?
|
||||||
|
local newItem: V1?
|
||||||
|
local shouldReuseKey = prevItems[key] == item and prevKeyForItem[key] ~= nil
|
||||||
|
|
||||||
|
if shouldReuseKey then
|
||||||
|
mappedKey = prevKeyForItem[key]
|
||||||
|
newItem = currentMapped[mappedKey :: K1]
|
||||||
else
|
else
|
||||||
mappedKeys[newKey] = key
|
local derivedItem, derivedKey = mapper(item, key)
|
||||||
|
newItem = derivedItem
|
||||||
|
if derivedKey == nil then
|
||||||
|
derivedKey = key :: any
|
||||||
|
end
|
||||||
|
mappedKey = derivedKey :: K1
|
||||||
|
if newItem == nil then
|
||||||
|
if currentMapped[mappedKey :: K1] ~= nil then
|
||||||
|
ensureWritable()
|
||||||
|
mappedItems[mappedKey :: K1] = nil
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if currentMapped[mappedKey :: K1] ~= newItem then
|
||||||
|
ensureWritable()
|
||||||
|
mappedItems[mappedKey :: K1] = newItem :: V1
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if mappedKey ~= nil and newItem ~= nil then
|
||||||
|
local existingKey = mappedKeys[mappedKey]
|
||||||
|
if existingKey ~= nil and existingKey ~= key then
|
||||||
|
error("mapped(): mapper returned duplicate key")
|
||||||
|
end
|
||||||
|
mappedKeys[mappedKey] = key
|
||||||
|
newPrevKeyForItem[key] = mappedKey
|
||||||
|
end
|
||||||
|
|
||||||
|
updatedPrevItems[key] = item
|
||||||
end
|
end
|
||||||
|
|
||||||
for key in next, prevMappedItems do
|
for key in next, prevMappedItems do
|
||||||
if mappedKeys[key] == nil and mappedItems[key] == prevMappedItems[key] then
|
if mappedKeys[key] == nil and currentMapped[key] == prevMappedItems[key] then
|
||||||
mappedItems[key] = nil
|
ensureWritable()
|
||||||
|
if mappedItems[key] ~= nil then
|
||||||
|
mappedItems[key] = nil
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
prevKeyForItem = newPrevKeyForItem
|
||||||
|
prevItems = updatedPrevItems
|
||||||
prevMappedItems = mappedItems
|
prevMappedItems = mappedItems
|
||||||
mappedAtom(mappedItems)
|
|
||||||
|
if changed then
|
||||||
|
mappedAtom(mappedItems)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unsubscribe = subscribe(callback, listener)
|
unsubscribe = subscribe(callback, listener)
|
||||||
|
|
|
||||||
|
|
@ -94,4 +94,51 @@ return function()
|
||||||
expect(added).to.equal(3)
|
expect(added).to.equal(3)
|
||||||
expect(deleted).to.equal(3)
|
expect(deleted).to.equal(3)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("only maps changed entries", function()
|
||||||
|
local itemsAtom = atom({ "a", "b" })
|
||||||
|
local mapperInvocations = 0
|
||||||
|
local mappedAtom = mapped(itemsAtom, function(value, key)
|
||||||
|
mapperInvocations += 1
|
||||||
|
return value, key
|
||||||
|
end)
|
||||||
|
|
||||||
|
local initialInvocations = mapperInvocations
|
||||||
|
itemsAtom({ "a", "b" })
|
||||||
|
expect(mapperInvocations).to.equal(initialInvocations)
|
||||||
|
|
||||||
|
itemsAtom({ "a", "c" })
|
||||||
|
expect(mapperInvocations).to.equal(initialInvocations + 1)
|
||||||
|
expect(mappedAtom()[2]).to.equal("c")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("omits entries when mapper returns nil", function()
|
||||||
|
local itemsAtom = atom({ "a", "b", "c" })
|
||||||
|
local mappedAtom = mapped(itemsAtom, function(value, key)
|
||||||
|
if key == 2 then
|
||||||
|
return nil, key
|
||||||
|
end
|
||||||
|
return value, key
|
||||||
|
end)
|
||||||
|
|
||||||
|
expect(mappedAtom()[1]).to.equal("a")
|
||||||
|
expect(mappedAtom()[2]).to.never.be.ok()
|
||||||
|
expect(mappedAtom()[3]).to.equal("c")
|
||||||
|
|
||||||
|
itemsAtom({ "a", "b", "c" })
|
||||||
|
expect(mappedAtom()[2]).to.never.be.ok()
|
||||||
|
|
||||||
|
itemsAtom({ "a", "b", "d" })
|
||||||
|
expect(mappedAtom()[2]).to.never.be.ok()
|
||||||
|
expect(mappedAtom()[3]).to.equal("d")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("throws on duplicate mapped keys", function()
|
||||||
|
local itemsAtom = atom({ "a", "b" })
|
||||||
|
expect(function()
|
||||||
|
mapped(itemsAtom, function(value)
|
||||||
|
return value, 1
|
||||||
|
end)
|
||||||
|
end).to.throw()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue