Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Row states fetching optimization 2022.4 #1790

Merged
merged 3 commits into from Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -30,7 +30,7 @@ import { IDataViewBodyUI } from "modules/DataView/DataViewUI";
import { TreeView } from "./TreeView";
import { observable } from "mobx";
import { getIsDataViewOrFormScreenWorking } from "model/selectors/DataView/getIsDataViewOrFormScreenWorking.1";
import { PubSub } from "utils/events";
import { EventHandler } from "utils/events";
import { isMobileLayoutActive } from "model/selectors/isMobileLayoutActive";
import { MobileDataViewHeader } from "gui/connections/MobileComponents/Grid/DataViewHeader";

Expand Down Expand Up @@ -154,7 +154,7 @@ export class DataViewInner extends React.Component<IDataViewProps> {
}

export class DataViewContext {
private tableKeyDownChannel = new PubSub();
private tableKeyDownChannel = new EventHandler();

subscribeTableKeyDownHandler(fn: (event: any) => void) {
return this.tableKeyDownChannel.subscribe(fn);
Expand Down
Expand Up @@ -22,7 +22,7 @@ import {
columnWidths,
context,
context2d,
currentRow,
currentRow, dataView,
drawingColumnIndex,
fixedColumnCount,
gridLeadCellDimensions,
Expand All @@ -31,7 +31,7 @@ import {
mouseMoveSubscriptions,
mouseOverSubscriptions,
propertyById,
realFixedColumnCount,
realFixedColumnCount, recordId,
rowHeight,
rowIndex,
scRenderCell,
Expand Down Expand Up @@ -59,6 +59,7 @@ import {
} from "gui/Components/ScreenElements/Table/TableRendering/DebugTableMonitor";
import { flow } from "mobx";
import selectors from "model/selectors-tree";
import { EventHandler } from "utils/events";

export function renderTable(
aCtx: any,
Expand Down Expand Up @@ -106,12 +107,22 @@ export function renderTable(
const ctx2d = context2d();
ctx2d.fillStyle = "white";
ctx2d.fillRect(0, 0, CPR() * viewportWidth(), CPR() * viewportHeight());
const i0 = firstDrawableRowIndex();
const i1 = lastDrawableRowIndex();
if (i0 === undefined || i1 === undefined) return;
for (let i = i0; i <= i1; i++) {
const firstRenderedIndex = Math.max(firstDrawableRowIndex() -5, 0);
const lastVisibleIndex = lastDrawableRowIndex() + 5;
if (firstRenderedIndex === undefined || lastVisibleIndex === undefined) return;
const visibleRowIds = [];
for (let i = firstRenderedIndex; i <= lastVisibleIndex; i++) {
rowIndex.set(i);
if (i <= lastVisibleIndex && currentRow()) {
visibleRowIds.push(recordId());
}
renderRow(i);
}
visibleRowsChanged.trigger(
{
dataSourceId: dataView().dataSource.identifier,
rowIds: visibleRowIds
});
setTableDebugRendered(context())
}
catch(error) {
Expand Down Expand Up @@ -148,8 +159,7 @@ function renderSecondLayerCells() {
setLayerIndex(0);
}

export function renderRow(rowIdx: number) {
rowIndex.set(rowIdx);
function renderRow(rowIdx: number) {
setCurrentRowRightBorderDrawn(false);
try {
if (!currentRow()) return;
Expand All @@ -173,3 +183,10 @@ export function renderCell(columnIdx: number) {
for (let d of scRenderCell) d();
}
}

export interface IVisibleRowData{
dataSourceId: string;
rowIds: string[];
}

export const visibleRowsChanged = new EventHandler<IVisibleRowData>();
4 changes: 2 additions & 2 deletions frontend-html/src/model/entities/OrigamAPI.ts
Expand Up @@ -35,7 +35,7 @@ import { IAboutInfo } from "./types/IAboutInfo";
import { T } from "utils/translation";
import fileDownload from "js-file-download";
import { ITableConfiguration } from "model/entities/TablePanelView/types/IConfigurationManager";
import { PubSub } from "utils/events";
import { EventHandler } from "utils/events";
import { layoutToString } from "model/entities/TablePanelView/layout";


Expand Down Expand Up @@ -81,7 +81,7 @@ export class OrigamAPI implements IApi {
return axiosInstance;
}

onApiResponse = new PubSub<{}>();
onApiResponse = new EventHandler<{}>();

errorHandler: (error: any) => void;
axiosInstance: AxiosInstance;
Expand Down
138 changes: 78 additions & 60 deletions frontend-html/src/model/entities/RowState.ts
Expand Up @@ -24,10 +24,12 @@ import { getApi } from "model/selectors/getApi";
import { getSessionId } from "model/selectors/getSessionId";
import { flashColor2htmlColor } from "utils/flashColorFormat";
import { IRowState, IRowStateColumnItem, IRowStateData, IRowStateItem } from "./types/IRowState";
import { FlowBusyMonitor } from "../../utils/flow";
import { FlowBusyMonitor } from "utils/flow";
import { handleError } from "model/actions/handleError";
import { visibleRowsChanged } from "gui/Components/ScreenElements/Table/TableRendering/renderTable";
import { getDataSource } from "model/selectors/DataSources/getDataSource";

const maxRowStatesInOneCall = 100;
const defaultRowStatesToFetch = 100;

export enum IIdState {
LOADING = "LOADING",
Expand All @@ -37,9 +39,17 @@ export enum IIdState {
export class RowState implements IRowState {
$type_IRowState: 1 = 1;
suppressWorkingStatus: boolean = false;
visibleRowIds: string[] = [];

constructor(data: IRowStateData) {
Object.assign(this, data);
visibleRowsChanged.subscribe((visibleRows) => {
const dataSource = getDataSource(this);
if (!visibleRows || dataSource.identifier !== visibleRows.dataSourceId) {
return;
}
this.visibleRowIds = visibleRows.rowIds;
});
}

monitor: FlowBusyMonitor = new FlowBusyMonitor();
Expand All @@ -49,88 +59,96 @@ export class RowState implements IRowState {
}

@observable firstLoadingPerformed = false;
@observable temporaryContainersValues?: Map<string, RowStateContainer>;
@observable temporaryRequestsValues?: Map<string, RowStateRequest>;
@computed get mayCauseFlicker() {
return !this.firstLoadingPerformed;
}

containers: Map<string, RowStateContainer> = new Map<string, RowStateContainer>();
requests: Map<string, RowStateRequest> = new Map<string, RowStateRequest>();

@observable
isSomethingLoading = false;

triggerLoadImm = flow(() => this.triggerLoad(maxRowStatesInOneCall));
triggerLoadImm = flow(() => this.triggerLoad(false));

*triggerLoad(rowStatesToLoad?: number): any {
private getRequestsToLoad(loadAll: boolean){
if(loadAll){
return this.requests.values()
}
return this.visibleRowIds.length === 0
? Array.from(this.requests.values()).slice(-defaultRowStatesToFetch)
: this.visibleRowIds.map(rowId => this.requests.get(rowId)!);
}

*triggerLoad(loadAll: boolean): any {
if (this.isSomethingLoading) {
return;
}
let containersToLoad: Map<string, RowStateContainer> = new Map();
let requestsToLoad: Map<string, RowStateRequest> = new Map();
let reportBusyStatus = true;
try {
while (true) {
try {
const containers = rowStatesToLoad
? Array.from(this.containers.values()).slice(-rowStatesToLoad)
: this.containers.values();
for (let container of containers) {
if (container.rowId && !container.isValid && !container.processingSate){
containersToLoad.set(container.rowId, container);
const requests = this.getRequestsToLoad(loadAll);

for (let request of requests) {
if (request.rowId && !request.isValid && !request.processingSate){
requestsToLoad.set(request.rowId, request);
}
}
reportBusyStatus = Array.from(containersToLoad.values()).every(container => !container.suppressWorkingStatus);
reportBusyStatus = Array.from(requestsToLoad.values()).every(requests => !requests.suppressWorkingStatus);
if (reportBusyStatus){
this.monitor.inFlow++;
}
if (containersToLoad.size === 0) {
if (requestsToLoad.size === 0) {
break;
}
for (let container of containersToLoad.values()) {
container.processingSate = IIdState.LOADING;
for (let request of requestsToLoad.values()) {
request.processingSate = IIdState.LOADING;
}
this.isSomethingLoading = true;
const api = getApi(this);
const states = yield api.getRowStates({
SessionFormIdentifier: getSessionId(this),
Entity: getEntity(this),
Ids: Array.from(containersToLoad.values()).map(container => container.rowId)
Ids: Array.from(requestsToLoad.values()).map(request => request.rowId)
});
this.isSomethingLoading = false;
this.firstLoadingPerformed = true;
for (let state of states) {
this.putValue(state);
this.containers.get(state.id)!.processingSate = undefined;
this.requests.get(state.id)!.processingSate = undefined;
}
} catch (error) {
this.isSomethingLoading = false;
this.firstLoadingPerformed = true;
for (let container of containersToLoad.values()) {
container.processingSate = IIdState.ERROR;
for (let request of requestsToLoad.values()) {
request.processingSate = IIdState.ERROR;
}
yield* handleError(this)(error);
} finally {
if(reportBusyStatus){
this.monitor.inFlow--;
}
containersToLoad.forEach(container => container.suppressWorkingStatus = false);
containersToLoad.clear();
requestsToLoad.forEach(request => request.suppressWorkingStatus = false);
requestsToLoad.clear();
}
}
} finally {
// After everything got loaded, here we switch back to provide the values just loaded.
this.temporaryContainersValues = undefined;
this.temporaryRequestsValues = undefined;
}
}
triggerLoadDebounced = _.debounce(this.triggerLoadImm, 666);
triggerLoadDebounced = _.debounce(this.triggerLoadImm, 200);

getValue(rowId: string) {
if (!this.containers.has(rowId)) {
this.containers.set(rowId, new RowStateContainer(rowId));
if (!this.requests.has(rowId)) {
this.requests.set(rowId, new RowStateRequest(rowId));
}
let container = this.containers.get(rowId)!;
if (!container.atom) {
container.suppressWorkingStatus = this.suppressWorkingStatus;
container.atom = createAtom(
let request = this.requests.get(rowId)!;
if (!request.atom) {
request.suppressWorkingStatus = this.suppressWorkingStatus;
request.atom = createAtom(
`RowState atom [${rowId}]`,
() =>
requestAnimationFrame(() => {
Expand All @@ -140,25 +158,25 @@ export class RowState implements IRowState {
}
)
}
container.atom.reportObserved?.();
if (this.temporaryContainersValues && this.temporaryContainersValues.has(rowId)) {
return this.temporaryContainersValues.get(rowId)?.rowStateItem;
request.atom.reportObserved?.();
if (this.temporaryRequestsValues && this.temporaryRequestsValues.has(rowId)) {
return this.temporaryRequestsValues.get(rowId)?.rowStateItem;
} else {
return this.containers.get(rowId)?.rowStateItem;
return this.requests.get(rowId)?.rowStateItem;
}
}

async loadValues(rowIds: string[]) {
for (const rowId of rowIds) {
if (!this.containers.has(rowId)) {
this.containers.set(rowId, new RowStateContainer(rowId));
if (!this.requests.has(rowId)) {
this.requests.set(rowId, new RowStateRequest(rowId));
}
}
await flow(this.triggerLoad.bind(this))();
await flow(() => this.triggerLoad(true))();
}

hasValue(rowId: string): boolean {
return this.containers.has(rowId);
return this.requests.has(rowId);
}

@action.bound
Expand All @@ -185,39 +203,39 @@ export class RowState implements IRowState {
new Set(state.disabledActions),
state.relations
);
if (!this.containers.has(state.id)) {
this.containers.set(state.id, new RowStateContainer(state.id));
if (!this.requests.has(state.id)) {
this.requests.set(state.id, new RowStateRequest(state.id));
}
const container = this.containers.get(state.id);
container!.rowStateItem = rowStateItem;
container!.isValid = true;
const request = this.requests.get(state.id);
request!.rowStateItem = rowStateItem;
request!.isValid = true;
this.firstLoadingPerformed = true;
}

@action.bound reload() {
// Store the rest of values to suppress flickering while reloading.
this.temporaryContainersValues = new Map(this.containers.entries());
this.temporaryRequestsValues = new Map(this.requests.entries());
// This actually causes reloading of the values (by views calling getValue(...) )
for (let rowStateContainer of this.containers.values()) {
rowStateContainer.atom?.onBecomeUnobservedListeners?.clear();
rowStateContainer.atom?.onBecomeObservedListeners?.clear();
rowStateContainer.atom = undefined;
rowStateContainer.isValid = false;
rowStateContainer.processingSate = undefined;
for (let rowStateRequest of this.requests.values()) {
rowStateRequest.atom?.onBecomeUnobservedListeners?.clear();
rowStateRequest.atom?.onBecomeObservedListeners?.clear();
rowStateRequest.atom = undefined;
rowStateRequest.isValid = false;
rowStateRequest.processingSate = undefined;
}
}

@action.bound clearAll() {
for (let rowStateContainer of this.containers.values()) {
rowStateContainer.atom?.onBecomeUnobservedListeners?.clear();
rowStateContainer.atom?.onBecomeObservedListeners?.clear();
rowStateContainer.atom = undefined;
rowStateContainer.isValid = false;
rowStateContainer.processingSate = undefined;
for (let rowStateRequest of this.requests.values()) {
rowStateRequest.atom?.onBecomeUnobservedListeners?.clear();
rowStateRequest.atom?.onBecomeObservedListeners?.clear();
rowStateRequest.atom = undefined;
rowStateRequest.isValid = false;
rowStateRequest.processingSate = undefined;
}
this.containers.clear();
this.requests.clear();
this.firstLoadingPerformed = false;
this.temporaryContainersValues = undefined;
this.temporaryRequestsValues = undefined;
// TODO: Wait when something is currently loading.
}

Expand Down Expand Up @@ -250,7 +268,7 @@ export class RowStateColumnItem implements IRowStateColumnItem {
}
}

class RowStateContainer {
class RowStateRequest {
public rowId: string;

@observable
Expand Down
4 changes: 2 additions & 2 deletions frontend-html/src/model/entities/types/IApi.ts
Expand Up @@ -22,7 +22,7 @@ import { IOrdering } from "./IOrderingConfiguration";
import { IServerSearchResult } from "model/entities/types/ISearchResult";
import { IAboutInfo } from "./IAboutInfo";
import { ITableConfiguration } from "model/entities/TablePanelView/types/IConfigurationManager";
import { PubSub } from "utils/events";
import { EventHandler } from "utils/events";

export interface IApi {
getAboutInfo(): Promise<IAboutInfo>;
Expand All @@ -37,7 +37,7 @@ export interface IApi {

createCanceller(): () => void;

onApiResponse: PubSub<{}>;
onApiResponse: EventHandler<{}>;

login(credentials: { UserName: string; Password: string }): Promise<string>;

Expand Down