From 41d144bc98e62f65c00fe5e393641f5158c7ba9e Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Fri, 23 Dec 2022 12:25:15 -0800 Subject: [PATCH] Use @indutny/sneequals for memoization --- ACKNOWLEDGMENTS.md | 46 ++++++++++---------- package.json | 2 +- ts/state/selectors/message.ts | 22 +++------- ts/state/selectors/timeline.ts | 7 +-- ts/util/proxyMemoize.ts | 78 ---------------------------------- yarn.lock | 10 ++--- 6 files changed, 35 insertions(+), 130 deletions(-) delete mode 100644 ts/util/proxyMemoize.ts diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index a46ce53f5a94..fd498dcd3c37 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -33,6 +33,28 @@ Signal Desktop makes use of the following open source projects. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## @indutny/sneequals + + Copyright Fedor Indutny, 2022. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ## @popperjs/core License: MIT @@ -1952,30 +1974,6 @@ Signal Desktop makes use of the following open source projects. License: MIT -## proxy-compare - - The MIT License (MIT) - - Copyright (c) 2020-2022 Daishi Kato - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - ## qrcode-generator License: MIT diff --git a/package.json b/package.json index 0ef5f14fc081..d21c667b3a22 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "dependencies": { "@formatjs/fast-memoize": "1.2.6", "@indutny/frameless-titlebar": "2.3.5", + "@indutny/sneequals": "1.0.1", "@popperjs/core": "2.11.6", "@react-spring/web": "9.5.5", "@signalapp/better-sqlite3": "8.0.4", @@ -143,7 +144,6 @@ "pino": "8.6.1", "protobufjs": "6.11.3", "proxy-agent": "5.0.0", - "proxy-compare": "2.3.0", "qrcode-generator": "1.4.4", "quill": "1.3.7", "quill-delta": "4.0.1", diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index da7a4f311a51..9ed3fc2afd61 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -7,6 +7,7 @@ import filesize from 'filesize'; import getDirection from 'direction'; import emojiRegex from 'emoji-regex'; import LinkifyIt from 'linkify-it'; +import { memoize } from '@indutny/sneequals'; import type { StateType } from '../reducer'; import type { @@ -56,7 +57,6 @@ import { isVoiceMessage, canBeDownloaded } from '../../types/Attachment'; import { ReadStatus } from '../../messages/MessageReadStatus'; import type { CallingNotificationType } from '../../util/callingNotification'; -import { proxyMemoize } from '../../util/proxyMemoize'; import { missingCaseError } from '../../util/missingCaseError'; import { getRecipients } from '../../util/getRecipients'; import { getOwn } from '../../util/getOwn'; @@ -270,7 +270,7 @@ export function getConversation( // Message -export const getAttachmentsForMessage = proxyMemoize( +export const getAttachmentsForMessage = memoize( ({ sticker, attachments = [], @@ -299,13 +299,10 @@ export const getAttachmentsForMessage = proxyMemoize( .filter(attachment => !attachment.error || canBeDownloaded(attachment)) .map(attachment => getPropsForAttachment(attachment)) .filter(isNotNil); - }, - { - name: 'getAttachmentsForMessage', } ); -export const processBodyRanges = proxyMemoize( +export const processBodyRanges = memoize( ( { bodyRanges }: Pick, options: { conversationSelector: GetConversationByIdType } @@ -327,9 +324,6 @@ export const processBodyRanges = proxyMemoize( }; }) .sort((a, b) => b.start - a.start); - }, - { - name: 'processBodyRanges', } ); @@ -489,7 +483,7 @@ const getPropsForStoryReplyContext = ( }; }; -export const getPropsForQuote = proxyMemoize( +export const getPropsForQuote = memoize( ( message: Pick< MessageWithUIFieldsType, @@ -555,9 +549,6 @@ export const getPropsForQuote = proxyMemoize( sentAt: Number(sentAt), text, }; - }, - { - name: 'getPropsForQuote', } ); @@ -642,7 +633,7 @@ function getTextDirection(body?: string): TextDirection { } } -export const getPropsForMessage = proxyMemoize( +export const getPropsForMessage = memoize( ( message: MessageWithUIFieldsType, options: GetPropsForMessageOptions @@ -747,9 +738,6 @@ export const getPropsForMessage = proxyMemoize( textDirection: getTextDirection(message.body), timestamp: message.sent_at, }; - }, - { - name: 'getPropsForMessage', } ); diff --git a/ts/state/selectors/timeline.ts b/ts/state/selectors/timeline.ts index 70d9c7b03c76..bed0a7cfbc83 100644 --- a/ts/state/selectors/timeline.ts +++ b/ts/state/selectors/timeline.ts @@ -1,8 +1,8 @@ // Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { memoize } from '@indutny/sneequals'; import type { TimelineItemType } from '../../components/conversation/TimelineItem'; -import { proxyMemoize } from '../../util/proxyMemoize'; import type { StateType } from '../reducer'; import type { MessageWithUIFieldsType } from '../ducks/conversations'; @@ -23,7 +23,7 @@ import { import { getActiveCall, getCallSelector } from './calling'; import { getPropsForBubble } from './message'; -const getTimelineItemInner = proxyMemoize( +const getTimelineItemInner = memoize( (message: MessageWithUIFieldsType, state: StateType): TimelineItemType => { const selectedMessage = getSelectedMessage(state); const conversationSelector = getConversationSelector(state); @@ -51,9 +51,6 @@ const getTimelineItemInner = proxyMemoize( activeCall, accountSelector, }); - }, - { - name: 'getTimelineItemInner', } ); diff --git a/ts/util/proxyMemoize.ts b/ts/util/proxyMemoize.ts deleted file mode 100644 index c4a9626e020a..000000000000 --- a/ts/util/proxyMemoize.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2022 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import { createProxy, getUntracked, isChanged } from 'proxy-compare'; - -export type ExcludeNull = Exclude; - -export type ProxyMemoizeOptions = Readonly<{ - // For debugging - name: string; - - equalityFn?: (prev: Result, next: Result) => boolean; -}>; - -export function proxyMemoize, Result>( - fn: (...params: Params) => ExcludeNull, - { equalityFn }: ProxyMemoizeOptions> -): (...param: Params) => ExcludeNull { - type CacheEntryType = Readonly<{ - params: Params; - result: ExcludeNull; - }>; - - const cache = new WeakMap(); - const affected = new WeakMap(); - const proxyCache = new WeakMap(); - const changedCache = new WeakMap(); - - return (...params: Params): ExcludeNull => { - if (params.length < 1) { - throw new Error('At least one parameter is required'); - } - - const cacheKey = params[0]; - - const entry = cache.get(cacheKey); - - if (entry && entry.params.length === params.length) { - let isValid = true; - for (const [i, cachedParam] of entry.params.entries()) { - // Proxy wasn't even touched - we are good to go. - const wasUsed = affected.has(cachedParam); - if (!wasUsed) { - continue; - } - - if (isChanged(cachedParam, params[i], affected, changedCache)) { - isValid = false; - break; - } - } - if (isValid) { - return entry.result; - } - } - - const proxies = params.map(param => - createProxy(param, affected, proxyCache) - ) as unknown as Params; - - const trackedResult = fn(...proxies); - const untrackedResult = getUntracked(trackedResult); - - // eslint-disable-next-line eqeqeq - let result = untrackedResult === null ? trackedResult : untrackedResult; - - // Try to reuse result if custom equality check is configured. - if (entry && equalityFn && equalityFn(entry.result, result)) { - ({ result } = entry); - } - - cache.set(cacheKey, { - params, - result, - }); - return result; - }; -} diff --git a/yarn.lock b/yarn.lock index fc525ac2545d..6054b18f62fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1607,6 +1607,11 @@ classnames "^2.2.6" deepmerge "^4.2.2" +"@indutny/sneequals@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@indutny/sneequals/-/sneequals-1.0.1.tgz#9e0fa9ab68038f640105b837ae8f9ee453ba0fac" + integrity sha512-UBWmN5kI4VDvV8/ratSqUGodYWiEhu2KlJqIqB5ga43CRhNXlrzhFg9ejM3PeoZaB9PP5hf8s2ZOOagAefYNhw== + "@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" @@ -14369,11 +14374,6 @@ proxy-agent@5.0.0: proxy-from-env "^1.0.0" socks-proxy-agent "^5.0.0" -proxy-compare@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.3.0.tgz#ac9633ae52918ff9c9fcc54dfe6316c7a02d20ee" - integrity sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ== - proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"