Skip to content

Commit

Permalink
perf(permission): add row and column cache to optimize performance (#…
Browse files Browse the repository at this point in the history
…3634)

Co-authored-by: GitHub Actions <actions@github.com>
  • Loading branch information
ybzky and actions-user authored Oct 9, 2024
1 parent 52f8305 commit 27eb2a3
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 23 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion mockdata/src/sheets/demo/default-workbook-data-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14167,7 +14167,7 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
},
body: {
dataStream:
'\u001Fhttps://univer-preview.vercel.app/sheets/\u001E\r12323\r\u001FA100\u001E\r\n',
'\u001Fhttps://univer-preview.vercel.app/sheets/\u001E\r12323\r\u001FA100\u001E\r\n',
textRuns: [],
paragraphs: [
{
Expand Down Expand Up @@ -24029,6 +24029,10 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
},
}),
},
{
name: 'SHEET_RANGE_PROTECTION_PLUGIN',
data: '{"sheet-0011":[{"ranges":[{"startRow":26,"startColumn":6,"endRow":30,"endColumn":8,"rangeType":0,"unitId":"workbook-01","sheetId":"sheet-0011"}],"permissionId":"nLNP3ABg","id":"IYg5","name":"工作表11(G27:I31)","unitType":3,"unitId":"workbook-01","subUnitId":"sheet-0011"}]}',
},
],
// namedRanges: [
// {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
* limitations under the License.
*/

import { DisposableCollection, Inject, IPermissionService, IUniverInstanceService, LifecycleStages, OnLifecycle, Optional, Rectangle, RxDisposable, UniverInstanceType } from '@univerjs/core';
import { UnitAction } from '@univerjs/protocol';
import { getSheetCommandTarget, RangeProtectionRuleModel, SheetsSelectionsService, WorkbookEditablePermission, WorksheetEditPermission, WorksheetSetCellStylePermission, WorksheetSetCellValuePermission, WorksheetSetColumnStylePermission, WorksheetSetRowStylePermission } from '@univerjs/sheets';
import type { ICellDataForSheetInterceptor, IRange, Nullable, Workbook } from '@univerjs/core';

import type { IRenderContext, IRenderModule, Scene, SpreadsheetSkeleton } from '@univerjs/engine-render';
import { DisposableCollection, Inject, IPermissionService, IUniverInstanceService, LifecycleStages, OnLifecycle, Optional, RANGE_TYPE, Rectangle, RxDisposable, UniverInstanceType } from '@univerjs/core';
import { UnitAction } from '@univerjs/protocol';

import { getSheetCommandTarget, RangeProtectionCache, RangeProtectionRuleModel, SheetsSelectionsService, WorkbookEditablePermission, WorksheetEditPermission, WorksheetSetCellStylePermission, WorksheetSetCellValuePermission, WorksheetSetColumnStylePermission, WorksheetSetRowStylePermission } from '@univerjs/sheets';
import { ISheetSelectionRenderService } from '../../services/selection/base-selection-render.service';
import { HeaderFreezeRenderController } from '../render-controllers/freeze.render-controller';
import { HeaderMoveRenderController } from '../render-controllers/header-move.render-controller';
Expand All @@ -43,6 +43,7 @@ export class SheetPermissionInterceptorCanvasRenderController extends RxDisposab
@Inject(HeaderMoveRenderController) private _headerMoveRenderController: HeaderMoveRenderController,
@ISheetSelectionRenderService private _selectionRenderService: ISheetSelectionRenderService,
@Inject(HeaderFreezeRenderController) private _headerFreezeRenderController: HeaderFreezeRenderController,
@Inject(RangeProtectionCache) private _rangeProtectionCache: RangeProtectionCache,
@Optional(HeaderResizeRenderController) private _headerResizeRenderController?: HeaderResizeRenderController
) {
super();
Expand All @@ -54,14 +55,15 @@ export class SheetPermissionInterceptorCanvasRenderController extends RxDisposab
}

private _initHeaderMovePermissionInterceptor() {
const headerMoveInterceptor = this._headerMoveRenderController.interceptor.getInterceptPoints().HEADER_MOVE_PERMISSION_CHECK;
this.disposeWithMe(
this._headerMoveRenderController.interceptor.intercept(this._headerMoveRenderController.interceptor.getInterceptPoints().HEADER_MOVE_PERMISSION_CHECK, {
this._headerMoveRenderController.interceptor.intercept(headerMoveInterceptor, {
handler: (defaultValue: Nullable<boolean>, selectionRange: IRange) => {
const target = getSheetCommandTarget(this._univerInstanceService);
if (!target) {
return false;
}
const { worksheet, unitId, subUnitId } = target;
const { unitId, subUnitId } = target;

const worksheetEditPermission = this._permissionService.composePermission([new WorkbookEditablePermission(unitId).id, new WorksheetEditPermission(unitId, subUnitId).id]).every((permission) => permission.value);
if (!worksheetEditPermission) {
Expand All @@ -72,26 +74,27 @@ export class SheetPermissionInterceptorCanvasRenderController extends RxDisposab
return true;
}

const protectionLapRange = this._rangeProtectionRuleModel.getSubunitRuleList(unitId, subUnitId).reduce((p, c) => {
return [...p, ...c.ranges];
}, [] as IRange[]).filter((range) => {
return Rectangle.intersects(range, selectionRange);
});
if (selectionRange.rangeType !== RANGE_TYPE.ROW && selectionRange.rangeType !== RANGE_TYPE.COLUMN) {
return defaultValue;
}

const haveNotPermission = protectionLapRange.some((range) => {
const { startRow, startColumn, endRow, endColumn } = range;
for (let row = startRow; row <= endRow; row++) {
for (let col = startColumn; col <= endColumn; col++) {
const permission = (worksheet.getCell(row, col) as (ICellDataForSheetInterceptor & { selectionProtection: ICellPermission[] }))?.selectionProtection?.[0];
if (permission?.[UnitAction.Edit] === false) {
return true;
}
if (selectionRange.rangeType === RANGE_TYPE.ROW) {
for (let i = selectionRange.startRow; i <= selectionRange.endRow; i++) {
const rowAllowed = this._rangeProtectionCache.getRowPermissionInfo(unitId, subUnitId, i, [UnitAction.Edit]);
if (rowAllowed === false) {
return false;
}
}
return false;
});
} else {
for (let i = selectionRange.startColumn; i <= selectionRange.endColumn; i++) {
const colAllowed = this._rangeProtectionCache.getColPermissionInfo(unitId, subUnitId, i, [UnitAction.Edit]);
if (colAllowed === false) {
return false;
}
}
}

return !haveNotPermission;
return true;
},
})
);
Expand Down
143 changes: 143 additions & 0 deletions packages/sheets/src/model/range-protection.cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export class RangeProtectionCache extends Disposable {
private readonly _cellRuleCache: Map<string, Map<string, Map<string, string>>> = new Map();
private readonly _permissionIdCache: Map<string, string> = new Map();
private readonly _cellInfoCache: Map<string, Map<string, Map<string, Partial<Record<UnitAction, boolean>> & { ruleId?: string; ranges?: IRange[] }>>> = new Map();
// {unitId:{subUnitId:{[row/col]:{permissionId1:{edit:true},permissionId2:{edit:true},permissionId3:{edit:false}}}}}
private readonly _rowInfoCache: Map<string, Map<string, Map<string, Map<string, Partial<Record<UnitAction, boolean>>>>>> = new Map();
private readonly _colInfoCache: Map<string, Map<string, Map<string, Map<string, Partial<Record<UnitAction, boolean>>>>>> = new Map();

constructor(
@Inject(RangeProtectionRuleModel) private readonly _ruleModel: RangeProtectionRuleModel,
Expand All @@ -36,6 +39,7 @@ export class RangeProtectionCache extends Disposable {
super();
this._initUpdateCellRuleCache();
this._initUpdateCellInfoCache();
this._initUpdateRowColInfoCache();
this._initCache();
}

Expand Down Expand Up @@ -137,6 +141,21 @@ export class RangeProtectionCache extends Disposable {
return cellMap;
}

private _ensureRowColInfoMap(unitId: string, subUnitId: string, type: 'row' | 'col') {
let subUnitMap = type === 'row' ? this._rowInfoCache.get(unitId) : this._colInfoCache.get(unitId);
if (!subUnitMap) {
subUnitMap = new Map();
type === 'row' ? this._rowInfoCache.set(unitId, subUnitMap) : this._colInfoCache.set(unitId, subUnitMap);
}
let cellMap = subUnitMap.get(subUnitId);

if (!cellMap) {
cellMap = new Map<string, Map<string, Partial<Record<UnitAction, boolean>>>>();
subUnitMap.set(subUnitId, cellMap);
}
return cellMap;
}

private _addCellRuleCache(ruleChange: IRuleChange) {
const { subUnitId, unitId, rule } = ruleChange;
const cellMap = this._ensureRuleMap(unitId, subUnitId);
Expand Down Expand Up @@ -172,6 +191,12 @@ export class RangeProtectionCache extends Disposable {
const cellInfoMap = this._ensureCellInfoMap(unitId, subUnitId);
cellRuleMap.clear();
cellInfoMap.clear();

const rowInfoMap = this._ensureRowColInfoMap(unitId, subUnitId, 'row');
const colInfoMap = this._ensureRowColInfoMap(unitId, subUnitId, 'col');
rowInfoMap.clear();
colInfoMap.clear();

this._ruleModel.getSubunitRuleList(unitId, subUnitId).forEach((rule) => {
const edit = this._permissionService.getPermissionPoint(new RangeProtectionPermissionEditPoint(unitId, subUnitId, rule.permissionId)?.id)?.value ?? true;
const view = this._permissionService.getPermissionPoint(new RangeProtectionPermissionViewPoint(unitId, subUnitId, rule.permissionId)?.id)?.value ?? true;
Expand All @@ -184,16 +209,132 @@ export class RangeProtectionCache extends Disposable {
rule.ranges.forEach((range) => {
const { startRow, endRow, startColumn, endColumn } = range;
for (let i = startRow; i <= endRow; i++) {
const rowInfo = rowInfoMap.get(`${i}`);
if (!rowInfo) {
rowInfoMap.set(`${i}`, new Map([[rule.id, { [UnitAction.Edit]: edit, [UnitAction.View]: view }]]));
} else {
rowInfo.set(rule.id, { [UnitAction.Edit]: edit, [UnitAction.View]: view });
}

for (let j = startColumn; j <= endColumn; j++) {
cellRuleMap.set(`${i}-${j}`, rule.id);
cellInfoMap.set(`${i}-${j}`, selectionProtection);
const colInfo = colInfoMap.get(`${j}`);
if (!colInfo) {
colInfoMap.set(`${j}`, new Map([[rule.id, { [UnitAction.Edit]: edit, [UnitAction.View]: view }]]));
} else {
colInfo.set(rule.id, { [UnitAction.Edit]: edit, [UnitAction.View]: view });
}
}
}
});
this._permissionIdCache.set(rule.permissionId, rule.id);
});
}

public getRowPermissionInfo(unitId: string, subUnitId: string, row: number, types: UnitAction[]) {
const rowInfo = this._rowInfoCache.get(unitId)?.get(subUnitId);
if (!rowInfo) {
return true;
}
const info = rowInfo.get(`${row}`);
if (!info) {
return true;
}
return types.every((type) => {
for (const actionGroup of info.values()) {
if (actionGroup[type] === false) {
return false;
}
}
return true;
});
}

public getColPermissionInfo(unitId: string, subUnitId: string, col: number, types: UnitAction[]) {
const colInfo = this._colInfoCache.get(unitId)?.get(subUnitId);
if (!colInfo) {
return true;
}
const info = colInfo.get(`${col}`);
if (!info) {
return true;
}
return types.every((type) => {
for (const actionGroup of info.values()) {
if (actionGroup[type] === false) {
return false;
}
}
return true;
});
}

private _initUpdateRowColInfoCache() {
this._permissionService.permissionPointUpdate$.pipe(
filter((permission) => permission.type === UnitObject.SelectRange),
map((permission) => permission as IRangePermissionPoint)
).subscribe({
next: (permission) => {
const { subUnitId, unitId, permissionId } = permission;
const ruleId = this._permissionIdCache.get(permissionId);
if (!ruleId) {
return;
}
const ruleInstance = this._ruleModel.getRule(unitId, subUnitId, ruleId);
if (!ruleInstance) {
return;
}

const rowInfoMap = this._ensureRowColInfoMap(unitId, subUnitId, 'row');
const colInfoMap = this._ensureRowColInfoMap(unitId, subUnitId, 'col');
const edit = this._permissionService.getPermissionPoint(new RangeProtectionPermissionEditPoint(unitId, subUnitId, ruleInstance.permissionId)?.id)?.value ?? true;
const view = this._permissionService.getPermissionPoint(new RangeProtectionPermissionViewPoint(unitId, subUnitId, ruleInstance.permissionId)?.id)?.value ?? true;

ruleInstance.ranges.forEach((range) => {
const { startRow, endRow, startColumn, endColumn } = range;
for (let i = startRow; i <= endRow; i++) {
const rowInfo = rowInfoMap.get(`${i}`);
if (!rowInfo) {
rowInfoMap.set(`${i}`, new Map([[ruleId, { [UnitAction.Edit]: edit, [UnitAction.View]: view }]]));
} else {
rowInfo.set(ruleId, { [UnitAction.Edit]: edit, [UnitAction.View]: view });
}

for (let j = startColumn; j <= endColumn; j++) {
const colInfo = colInfoMap.get(`${j}`);
if (!colInfo) {
colInfoMap.set(`${j}`, new Map([[ruleId, { [UnitAction.Edit]: edit, [UnitAction.View]: view }]]));
} else {
colInfo.set(ruleId, { [UnitAction.Edit]: edit, [UnitAction.View]: view });
}
}
}
});
},
});

this._ruleModel.ruleChange$.subscribe((info) => {
if (info.type === 'delete') {
const { unitId, subUnitId, rule } = info;
const rowInfoMap = this._ensureRowColInfoMap(unitId, subUnitId, 'row');
const colInfoMap = this._ensureRowColInfoMap(unitId, subUnitId, 'col');
rule.ranges.forEach((range) => {
const { startRow, endRow, startColumn, endColumn } = range;
for (let i = startRow; i <= endRow; i++) {
const rowInfo = rowInfoMap.get(`${i}`);
rowInfo?.delete(rule.id);

for (let j = startColumn; j <= endColumn; j++) {
const colInfo = colInfoMap.get(`${j}`);
colInfo?.delete(rule.id);
}
}
});
}
});
}

public getCellInfo(unitId: string, subUnitId: string, row: number, col: number) {
const cellMap = this._ensureCellInfoMap(unitId, subUnitId);
const cacheValue = cellMap.get(`${row}-${col}`);
Expand Down Expand Up @@ -226,6 +367,8 @@ export class RangeProtectionCache extends Disposable {
public deleteUnit(unitId: string) {
this._cellRuleCache.delete(unitId);
this._cellInfoCache.delete(unitId);
this._rowInfoCache.delete(unitId);
this._colInfoCache.delete(unitId);
const workbook = this._univerInstanceService.getUnit<Workbook>(unitId);
workbook?.getSheets().forEach((sheet) => {
const subUnitId = sheet.getSheetId();
Expand Down

0 comments on commit 27eb2a3

Please sign in to comment.