Skip to content

SharedTree Batch vs Single Synchronous Transaction in EditableTree #13285

@milanro

Description

@milanro

EditableTree so far offers only automatic transaction per command. No batch API is available for the case, that we want to change different places in the tree at once. This has the consistency and the performance impact.

I recorded the video showing these impacts:
I have a table with rows and cells. At the beginning, all cells are set to 0. I want to increase the number in each cell to 5 by executing cell++ in each cell and repeating it 5 times. I execute this 3 times within 1 transaction and then once so that each cell++ is executed in it's own transaction. The execution at the action site takes relatively the same time. However the receiving client reacts differently at single / multi transaction. Single transaction brings the receiving client from consistent state (all cells 0) to consistent state (all cells 5) almost immediately. Multi transactions are slowly increasing numbers at cells, the inconsistent states are visible and the performance is poor.

SharedTreeBatchTx.webm

I implemented a very simple POC to enable batch transactions at Editable Tree. The idea is to have the member attribute transactionContext: { editor: DefaultEditBuilder }| undefined in the ProxyContext (which implements EditableTreeContext). If that attribute is not undefined, the runTransaction method does not open the new transaction but it rather uses the editor from the transactionContext. There is an entry method in the EditableTreeContext.editInSynchronousTransaction which opens the transaction, sets it to transactionContext and then calls the code passed via parameter.

The code change in editableTreeContext.ts at the ProxyContext class is like :

    private transactionContext: { editor: DefaultEditBuilder } | undefined = undefined;

    public editInSynchronousTransaction(edits: () => void): boolean {
        assert(
            this.transactionCheckout !== undefined,
            0x45a /* `transactionCheckout` is required to edit the EditableTree */,
        );
        const result = runSynchronousTransaction(
            this.transactionCheckout,
            (forest: IForestSubscription, editor: DefaultEditBuilder) => {
                try {
                    this.transactionContext = { editor };
                    edits();
                    return TransactionResult.Apply;
                } finally {
                    this.transactionContext = undefined;
                }
            },
        );
        return result === TransactionResult.Apply;
    }

    private runTransaction(transaction: (editor: DefaultEditBuilder) => void): boolean {
        assert(
            this.transactionCheckout !== undefined,
            0x45a /* `transactionCheckout` is required to edit the EditableTree */,
        );
        let result;
        if (this.transactionContext !== undefined) {
            transaction(this.transactionContext.editor);
            result = TransactionResult.Apply;
        } else {
            result = runSynchronousTransaction(
                this.transactionCheckout,
                (forest: IForestSubscription, editor: DefaultEditBuilder) => {
                    transaction(editor);
                    return TransactionResult.Apply;
                },
            );
        }
        return result === TransactionResult.Apply;
    }

Then there is an addition in the editableTreeContext.ts at the EditableTreeContext interface :

/**
     * This method runs the code specified by the edits parameter in the synchronous transaction.
     * The editing code is supposed to perform edits only by calling edit methods within this instance,
     * such as setValue, insertNodes etc.
     * @param edits - The code calling edit methods within this instance.
     * @returns True if transaction was applied, false if it was aborted.
     */
    editInSynchronousTransaction(edits: () => void): boolean;

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions