perf: reduce unused provider connection (#3090)

This commit is contained in:
Alex Yang
2023-07-07 16:13:32 +08:00
committed by GitHub
parent 152fbaabda
commit 3294043180
5 changed files with 48 additions and 41 deletions

View File

@@ -8,6 +8,7 @@ import type { WorkspaceFlavour } from '@affine/env/workspace';
import { import {
rootCurrentWorkspaceIdAtom, rootCurrentWorkspaceIdAtom,
rootWorkspacesMetadataAtom, rootWorkspacesMetadataAtom,
workspaceAdaptersAtom,
} from '@affine/workspace/atom'; } from '@affine/workspace/atom';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import type { ActiveDocProvider } from '@blocksuite/store'; import type { ActiveDocProvider } from '@blocksuite/store';
@@ -22,7 +23,7 @@ const logger = new DebugLogger('web:atoms:root');
*/ */
export const workspacesAtom = atom<Promise<AllWorkspace[]>>( export const workspacesAtom = atom<Promise<AllWorkspace[]>>(
async (get, { signal }) => { async (get, { signal }) => {
const { WorkspaceAdapters } = await import('../adapters/workspace'); const WorkspaceAdapters = get(workspaceAdaptersAtom);
const flavours: string[] = Object.values(WorkspaceAdapters).map( const flavours: string[] = Object.values(WorkspaceAdapters).map(
plugin => plugin.flavour plugin => plugin.flavour
); );
@@ -87,7 +88,7 @@ export const workspacesAtom = atom<Promise<AllWorkspace[]>>(
*/ */
export const rootCurrentWorkspaceAtom = atom<Promise<AllWorkspace>>( export const rootCurrentWorkspaceAtom = atom<Promise<AllWorkspace>>(
async (get, { signal }) => { async (get, { signal }) => {
const { WorkspaceAdapters } = await import('../adapters/workspace'); const WorkspaceAdapters = get(workspaceAdaptersAtom);
const metadata = await get(rootWorkspacesMetadataAtom); const metadata = await get(rootWorkspacesMetadataAtom);
const targetId = get(rootCurrentWorkspaceIdAtom); const targetId = get(rootCurrentWorkspaceIdAtom);
if (targetId === null) { if (targetId === null) {

View File

@@ -5,8 +5,10 @@ import {
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ContactWithUsIcon } from '@blocksuite/icons'; import { ContactWithUsIcon } from '@blocksuite/icons';
import type { PassiveDocProvider } from '@blocksuite/store';
import { noop } from 'foxact/noop';
import type React from 'react'; import type React from 'react';
import { useCallback, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useWorkspaces } from '../../../hooks/use-workspaces'; import { useWorkspaces } from '../../../hooks/use-workspaces';
@@ -19,7 +21,7 @@ import {
} from './general-setting'; } from './general-setting';
import { SettingSidebar } from './setting-sidebar'; import { SettingSidebar } from './setting-sidebar';
import { settingContent } from './style.css'; import { settingContent } from './style.css';
import { WorkSpaceSetting } from './workspace-setting'; import { WorkspaceSetting } from './workspace-setting';
type ActiveTab = GeneralSettingKeys | 'workspace' | 'account'; type ActiveTab = GeneralSettingKeys | 'workspace' | 'account';
export type SettingProps = { export type SettingProps = {
@@ -70,6 +72,24 @@ export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
onSettingClick({ activeTab: 'account', workspace: null }); onSettingClick({ activeTab: 'account', workspace: null });
}, [onSettingClick]); }, [onSettingClick]);
useEffect(() => {
if (workspace && workspace !== currentWorkspace) {
const providers = workspace.blockSuiteWorkspace.providers.filter(
(provider): provider is PassiveDocProvider =>
'passive' in provider && provider.passive
);
providers.forEach(provider => {
provider.connect();
});
return () => {
providers.forEach(provider => {
provider.disconnect();
});
};
}
return noop;
}, [currentWorkspace, workspace]);
return ( return (
<SettingModalBase open={open} setOpen={setOpen}> <SettingModalBase open={open} setOpen={setOpen}>
<SettingSidebar <SettingSidebar
@@ -87,7 +107,7 @@ export const SettingModal: React.FC<SettingModalProps & SettingProps> = ({
<div className="wrapper"> <div className="wrapper">
<div className="content"> <div className="content">
{activeTab === 'workspace' && workspace ? ( {activeTab === 'workspace' && workspace ? (
<WorkSpaceSetting key={workspace.id} workspace={workspace} /> <WorkspaceSetting key={workspace.id} workspace={workspace} />
) : null} ) : null}
{generalSettingList.find(v => v.key === activeTab) ? ( {generalSettingList.find(v => v.key === activeTab) ? (
<GeneralSetting generalKey={activeTab as GeneralSettingKeys} /> <GeneralSetting generalKey={activeTab as GeneralSettingKeys} />

View File

@@ -5,7 +5,7 @@ import { useOnTransformWorkspace } from '../../../../hooks/root/use-on-transform
import { useAppHelper } from '../../../../hooks/use-workspaces'; import { useAppHelper } from '../../../../hooks/use-workspaces';
import type { AllWorkspace } from '../../../../shared'; import type { AllWorkspace } from '../../../../shared';
export const WorkSpaceSetting = ({ export const WorkspaceSetting = ({
workspace, workspace,
}: { }: {
workspace: AllWorkspace; workspace: AllWorkspace;

View File

@@ -99,27 +99,7 @@ export const QuickSearch: FC = () => {
export const AllWorkspaceContext = ({ export const AllWorkspaceContext = ({
children, children,
}: PropsWithChildren): ReactElement => { }: PropsWithChildren): ReactElement => {
const currentWorkspaceId = useAtomValue(rootCurrentWorkspaceIdAtom); useWorkspaces();
const workspaces = useWorkspaces();
useEffect(() => {
const providers = workspaces
// ignore current workspace
.filter(workspace => workspace.id !== currentWorkspaceId)
.flatMap(workspace =>
workspace.blockSuiteWorkspace.providers.filter(
(provider): provider is PassiveDocProvider =>
'passive' in provider && provider.passive
)
);
providers.forEach(provider => {
provider.connect();
});
return () => {
providers.forEach(provider => {
provider.disconnect();
});
};
}, [currentWorkspaceId, workspaces]);
return <>{children}</>; return <>{children}</>;
}; };

View File

@@ -1,3 +1,4 @@
import { DebugLogger } from '@affine/debug';
import type { import type {
LocalIndexedDBBackgroundProvider, LocalIndexedDBBackgroundProvider,
LocalIndexedDBDownloadProvider, LocalIndexedDBDownloadProvider,
@@ -13,26 +14,25 @@ import {
} from '@toeverything/y-indexeddb'; } from '@toeverything/y-indexeddb';
import type { Doc } from 'yjs'; import type { Doc } from 'yjs';
import { CallbackSet } from '../utils';
import { localProviderLogger as logger } from './logger';
import { import {
createSQLiteDBDownloadProvider, createSQLiteDBDownloadProvider,
createSQLiteProvider, createSQLiteProvider,
} from './sqlite-providers'; } from './sqlite-providers';
const Y = Workspace.Y; const Y = Workspace.Y;
const logger = new DebugLogger('indexeddb-provider');
const createIndexedDBBackgroundProvider: DocProviderCreator = ( const createIndexedDBBackgroundProvider: DocProviderCreator = (
id, id,
blockSuiteWorkspace blockSuiteWorkspace
): LocalIndexedDBBackgroundProvider => { ): LocalIndexedDBBackgroundProvider => {
const indexeddbProvider = create(blockSuiteWorkspace); const indexeddbProvider = create(blockSuiteWorkspace);
const callbacks = new CallbackSet(); let connected = false;
return { return {
flavour: 'local-indexeddb-background', flavour: 'local-indexeddb-background',
passive: true, passive: true,
get connected() { get connected() {
return callbacks.ready; return connected;
}, },
cleanup: () => { cleanup: () => {
indexeddbProvider.cleanup().catch(console.error); indexeddbProvider.cleanup().catch(console.error);
@@ -42,27 +42,27 @@ const createIndexedDBBackgroundProvider: DocProviderCreator = (
indexeddbProvider.connect(); indexeddbProvider.connect();
indexeddbProvider.whenSynced indexeddbProvider.whenSynced
.then(() => { .then(() => {
callbacks.ready = true; connected = true;
callbacks.forEach(cb => cb());
}) })
.catch(error => { .catch(error => {
callbacks.ready = false; connected = false;
if (error instanceof EarlyDisconnectError) { if (error instanceof EarlyDisconnectError) {
return; return;
} else {
throw error;
} }
throw error;
}); });
}, },
disconnect: () => { disconnect: () => {
assertExists(indexeddbProvider); assertExists(indexeddbProvider);
logger.info('disconnect indexeddb provider', id); logger.info('disconnect indexeddb provider', id);
indexeddbProvider.disconnect(); indexeddbProvider.disconnect();
callbacks.ready = false; connected = false;
}, },
}; };
}; };
const cache: WeakMap<Doc, Uint8Array> = new WeakMap();
const createIndexedDBDownloadProvider: DocProviderCreator = ( const createIndexedDBDownloadProvider: DocProviderCreator = (
id, id,
doc doc
@@ -74,11 +74,17 @@ const createIndexedDBDownloadProvider: DocProviderCreator = (
_reject = reject; _reject = reject;
}); });
async function downloadBinaryRecursively(doc: Doc) { async function downloadBinaryRecursively(doc: Doc) {
const binary = await downloadBinary(doc.guid); if (cache.has(doc)) {
if (binary) { const binary = cache.get(doc) as Uint8Array;
Y.applyUpdate(doc, binary); Y.applyUpdate(doc, binary);
await Promise.all([...doc.subdocs].map(downloadBinaryRecursively)); } else {
const binary = await downloadBinary(doc.guid);
if (binary) {
Y.applyUpdate(doc, binary);
cache.set(doc, binary);
}
} }
await Promise.all([...doc.subdocs].map(downloadBinaryRecursively));
} }
return { return {
flavour: 'local-indexeddb', flavour: 'local-indexeddb',
@@ -90,7 +96,7 @@ const createIndexedDBDownloadProvider: DocProviderCreator = (
// todo: cleanup data // todo: cleanup data
}, },
sync: () => { sync: () => {
logger.info('connect indexeddb provider', id); logger.info('sync indexeddb provider', id);
downloadBinaryRecursively(doc).then(_resolve).catch(_reject); downloadBinaryRecursively(doc).then(_resolve).catch(_reject);
}, },
}; };