diff --git a/benches/mapped.luau b/benches/mapped.luau index 27ca6bd..695b7f5 100644 --- a/benches/mapped.luau +++ b/benches/mapped.luau @@ -3,55 +3,56 @@ local bench = require("./bench") local computed = Charm.computed local mapped = Charm.mapped -local setActiveSub = Charm.setActiveSub local signal = Charm.signal -type Entry = { number } +type Key = number +type Value = number local count = 512 -local initialState: { Entry } = table.create(count) +local initialState: { [Key]: Value } = table.create(count, 0) -for i = 1, count do - initialState[i] = table.create(64, i) +local function expensiveOperation() + for _ = 1, 100 do + continue + end end -local function mapper(value: Entry, key: number): (Entry, number) - return table.clone(value), key + 1 -end - -local function simpleMapped(source: () -> { Entry }, mapper: (Entry, number) -> (Entry, number)): () -> { Entry } +local function simpleMapped( + source: () -> { [Key]: Value }, + mapper: (Value, Key) -> (Value, Key?) +): () -> { [Key]: Value } return computed(function() local state = source() local nextState = {} - for index, value in state do - local currentSub = setActiveSub(nil) - local success, valueOut, keyOut = pcall(mapper, value, index) - setActiveSub(currentSub) - - if success then - nextState[keyOut] = valueOut - end + for keyIn, valueIn in state do + local valueOut, keyOut = mapper(valueIn, keyIn) + nextState[if keyOut == nil then keyIn else keyOut] = valueOut end - return state + return nextState end) end -local function updatePercentage(percent: number): ({ Entry }) -> { Entry } +local function updateRandomly(updateChance: number): ({ [Key]: Value }) -> { [Key]: Value } return function(state) local nextState = table.clone(state) - for index, value in state do - if math.random() < percent then - nextState[index] = table.clone(value) + for key in state do + if math.random() < updateChance then + nextState[key] += 1 end end return nextState end end -for _, percent in { 0.1, 0.5, 1 } do - bench.run(`mapped {percent}`, function() +for _, updateChance in { 0.1, 0.5, 1 } do + local function mapper(value: Value, key: Key): (Value, Key?) + expensiveOperation() + return value + 1, key + 1 + end + + bench.run(`mapped {updateChance}`, function() local src, setSrc = signal(initialState) local out = mapped(src, mapper) @@ -59,14 +60,14 @@ for _, percent in { 0.1, 0.5, 1 } do out() bench.profileend() - setSrc(updatePercentage(percent)) + setSrc(updateRandomly(updateChance)) bench.profilebegin("update") out() bench.profileend() end) - bench.run(`control {percent}`, function() + bench.run(`control {updateChance}`, function() local src, setSrc = signal(initialState) local out = simpleMapped(src, mapper) @@ -74,7 +75,7 @@ for _, percent in { 0.1, 0.5, 1 } do out() bench.profileend() - setSrc(updatePercentage(percent)) + setSrc(updateRandomly(updateChance)) bench.profilebegin("update") out() diff --git a/packages/charm/src/init.luau b/packages/charm/src/init.luau index 92be073..46be53c 100644 --- a/packages/charm/src/init.luau +++ b/packages/charm/src/init.luau @@ -914,22 +914,9 @@ end @see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-mapper ]] local function mapped(getter: Getter<{ [KI]: VI }>, mapper: (VI, KI) -> (VO, KO)): Getter<{ [KO]: VO }> - local map = noYield(mapper) local nextInput: { [KI]: VI } = {} local keyMap: { [KI]: KO } = {} - local function mapUntracked(value: VI, key: KI): (VO, KO) - local prevSub = setActiveSub(nil) - local success, valueOut, keyOut = pcall(map, value, key) - activeSub = prevSub - - if not success then - error(`mapped() transform function failed: {valueOut}`, 0) - end - - return valueOut, if keyOut == nil then key :: any else keyOut - end - return computed(function(lastOutput: { [KO]: VO }?): { [KO]: VO } local nextOutput: { [KO]: VO } = lastOutput or {} local prevInput = nextInput @@ -937,19 +924,19 @@ local function mapped(getter: Getter<{ [KI]: VI }>, mapper: (VI, -- For keys that were removed from the input, remove the corresponding -- keys from the output. - for prevKeyIn in prevInput do - if nextInput[prevKeyIn] ~= nil then + for keyIn in prevInput do + if nextInput[keyIn] ~= nil then continue end - local prevKeyOut = keyMap[prevKeyIn] + local keyOut = keyMap[keyIn] - if prevKeyOut ~= nil then + if keyOut ~= nil then if nextOutput == lastOutput then nextOutput = table.clone(nextOutput) end - nextOutput[prevKeyOut] = nil - keyMap[prevKeyIn] = nil + nextOutput[keyOut] = nil + keyMap[keyIn] = nil end end @@ -957,13 +944,14 @@ local function mapped(getter: Getter<{ [KI]: VI }>, mapper: (VI, -- the output. If the mapper returns a new key that is different from -- the previous call, the old mapped key should be removed from the -- output. - for nextKeyIn, nextValueIn in nextInput do - if nextValueIn == prevInput[nextKeyIn] then + for keyIn, nextValueIn in nextInput do + if nextValueIn == prevInput[keyIn] then continue end - local nextValueOut, nextKeyOut = mapUntracked(nextValueIn, nextKeyIn) - local prevKeyOut = keyMap[nextKeyIn] + local nextValueOut, keyOutOrNil = mapper(nextValueIn, keyIn) + local nextKeyOut: KO = if keyOutOrNil ~= nil then keyOutOrNil else keyIn :: any + local prevKeyOut = keyMap[keyIn] if prevKeyOut ~= nil and prevKeyOut ~= nextKeyOut then if nextOutput == lastOutput then @@ -971,13 +959,13 @@ local function mapped(getter: Getter<{ [KI]: VI }>, mapper: (VI, end nextOutput[prevKeyOut] = nil nextOutput[nextKeyOut] = nextValueOut - keyMap[nextKeyIn] = nextKeyOut + keyMap[keyIn] = nextKeyOut elseif nextOutput[nextKeyOut] ~= nextValueOut then if nextOutput == lastOutput then nextOutput = table.clone(nextOutput) end nextOutput[nextKeyOut] = nextValueOut - keyMap[nextKeyIn] = nextKeyOut + keyMap[keyIn] = nextKeyOut end end