HTML Preparation code:
AخA
 
1
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.6.7/rxjs.umd.js"></script>
Script Preparation code:
x
 
function addMutation(idMutations, typeMutations, action, mutation) {
    const {
        block
    } = mutation;
    const mutations = idMutations[block.id] || new BlockMutations();
    idMutations[block.id] = mutations;
    mutations.add(mutation);
    const mutations2 = typeMutations[block.type] || new BlockMutations();
    typeMutations[block.type] = mutations2;
    mutations2.add(mutation);
}
BlockMutations = class BlockMutations {
    constructor(useFilter) {
        this.useFilter = useFilter;
        this.mutations = [];
        this.idMutations = {};
        this.typeMutations = {};
        this.actionMutations = [];
        this.keyedMutations = {};
        this.filters = [
            mutation => mutation.block.type,
        ];
    }
    add(mutation) {
        if (!this.actionMutations[mutation.action]) {
            this.actionMutations[mutation.action] = [];
        }
        this.actionMutations[mutation.action].push(mutation);
    }
    finalize() {
        const {
            removed,
            added,
            updated
        } = this.actionMutations;
        this.finalizeOne(removed);
        this.finalizeOne(added);
        this.finalizeOne(updated);
    }
    finalizeOne(mutations) {
        const keyedMutations = this.keyedMutations;
        const filters = this.filters;
        for (const mutation of mutations) {
            this.mutations.push(mutation);
            const {
                block
            } = mutation;
            const ims = this.idMutations[block.id] || (this.idMutations[block.id] = []);
            ims.push(mutation);
            if (this.useFilter) {
                for (const filter of filters) {
                    const key = filter(mutation);
                    if (key) {
                        const mutations = keyedMutations[key] || (keyedMutations[key] = []);
                        mutations.push(mutation);
                    }
                }
            } else {
                const tms = this.typeMutations[block.id] || (this.typeMutations[block.type] = []);
                tms.push(mutation);
            }
        }
    }
    dispatch(listeners) {
        this.finalize();
        for (const {
                callback,
                condition
            } of listeners) {
            if (condition) {
                const {
                    type,
                    id
                } = condition;
                const mutations = type ? (this.useFilter ? this.keyedMutations[type] : this.typeMutations[type]) : id ? this.idMutations[id] : null;
                if (mutations) {
                    callback(mutations);
                }
            } else {
                callback(this.mutations);
            }
        }
    }
}
class BlockMutationsIterator {
    constructor(mutations, firstIndex, indices) {
        this.mutations = mutations;
        this.firstIndex = firstIndex;
        this.indices = indices;
        this.cursor = -2;
    }
    hasNext() {
        return this.cursor === -2 || this.cursor >= 0;
    }
    next() {
        const cursor = this.cursor === -2 ? this.firstIndex : this.cursor;
        if (cursor < 0) {
            return;
        }
        const index = this.indices[cursor];
        this.cursor = this.indices[cursor + 1];
        return this.mutations[index];
    }
}
BlockMutations2 = class BlockMutations2 {
    constructor(x) {
        this.mutations = [];
        this.actionMutationMap = {};
        this.idMutationMap = {};
        this.typeMutationMap = {};
    }
    add(mutation) {
        const {
            action,
            block
        } = mutation;
        const {
            actionMutationMap,
            idMutationMap,
            typeMutationMap
        } = this;
        this.mutations.push(mutation);
        if (!actionMutationMap[action]) {
            actionMutationMap[action] = [];
        }
        actionMutationMap[action].push(mutation);
        if (!idMutationMap[block.id]) {
            idMutationMap[block.id] = [];
        }
        idMutationMap[block.id].push(mutation);
        if (!typeMutationMap[block.type]) {
            typeMutationMap[block.type] = [];
        }
        typeMutationMap[block.type].push(mutation);
    }
    finalize() {}
    dispatch(listeners) {
        const {
            typeMutationMap,
            idMutationMap,
            actionMutationMap
        } = this;
        for (const {
                callback,
                condition
            } of listeners) {
            if (condition) {
                const {
                    type,
                    id
                } = condition;
                if (type) {
                    if (typeMutationMap[type]) {
                        callback(typeMutationMap[type]);
                    }
                    continue;
                }
                if (id) {
                    if (idMutationMap[id]) {
                        callback(idMutationMap[id]);
                    }
                    continue;
                }
            } else {
                callback(actionMutationMap);
            }
        }
    }
}
const DEFAULT_ID = 0;
const ACTION_TYPES = {
    added: 0,
    removed: 1,
    updated: 2,
    moved: 3,
};
function updateIndex(index, offset, lasts, keys, firsts, key, action) {
    const lastIndex = lasts[offset];
    const lastActionIndex = lasts[offset + action];
    const keyOffset = keys.length;
    if (lastIndex >= 0) {
        keys[lastIndex] = keyOffset + 1;
        keys[lastActionIndex] = keyOffset + 2;
    }
    lasts[offset] = keyOffset + 1;
    lasts[offset + action] = keyOffset + 2;
    keys[keyOffset] = index;
    keys[keyOffset + 1] = -1;
    keys[keyOffset + 2] = -1;
    if (firsts[offset] === undefined) {
        firsts[offset] = keyOffset;
    }
    if (firsts[offset + action] === undefined) {
        firsts[offset + action] = keyOffset;
    }
}
BlockMutations3 = class BlockMutations3 {
    constructor() {
        this.mutations = [];
        this.idOffsetMap = {};
        this.typeOffsetMap = {};
        // self, action1, action2, action3, ...
        this.idFirsts = [];
        // self, action1, action2, action3, ...
        this.typeFirsts = [];
        // self, action1, action2, action3, ...
        this.idLasts = [];
        // self, action1, action2, action3, ...
        this.typeLasts = [];
        // mutation index, same id (without action) next, same action next, 
        this.ids = [];
        // mutation index, same type (without action) next, same action next
        this.types = [];
    }
    add(mutation) {
        const {
            block,
            action
        } = mutation;
        const {
            id,
            type
        } = block;
        const index = this.mutations.push(mutation) - 1;
        const actionIndex = ACTION_TYPES[action] + 1;
        const idOffset = this.idOffsetMap[id] || (this.idOffsetMap[id] = this.idFirsts.length);
        const typeOffset = this.typeOffsetMap[type] || (this.typeOffsetMap[type] = this.typeFirsts.length);
        updateIndex(index, idOffset, this.idLasts, this.ids, this.idFirsts, block.id, actionIndex);
        updateIndex(index, typeOffset, this.typeLasts, this.types, this.typeFirsts, block.type, actionIndex);
    }
    dispatch(listeners) {
        const {
            mutations,
            typeOffsetMap,
            idOffsetMap,
            types,
            ids
        } = this;
        for (const {
                callback,
                condition
            } of listeners) {
            if (condition) {
                const {
                    type,
                    id
                } = condition;
                if (type) {
                    if (typeOffsetMap[type]) {
                        callback(this);
                    }
                    continue;
                }
                if (id) {
                    if (idOffsetMap[id]) {
                        callback(this);
                    }
                    continue;
                }
            } else {
                callback(this);
            }
        }
    }
}
const Subject = rxjs.Subject;
const cb = () => {};
class BlockModel {
    constructor(id, type) {
        this.id = id;
        this.type = type;
        this.update$ = new Subject();
        this.attach$ = new Subject();
        this.detach$ = new Subject();
        this.structUpdate$ = new Subject();
        this.destroy$ = new Subject();
        this.update$.subscribe(cb);
        this.attach$.subscribe(cb);
        this.detach$.subscribe(cb);
        this.structUpdate$.subscribe(cb);
        this.destroy$.subscribe(cb);
    }
}
blockManager = new class BlockModelManager {
    constructor() {
        this.blockModelCreate$ = new Subject();
        this.blockModelUpdate$ = new Subject();
        this.blockModelAttached$ = new Subject();
        this.blockModelDetached$ = new Subject();
        this.blockModelDestroy$ = new Subject();
        this.blockModelStructUpdate$ = new Subject();
        this.blockModelCreate$.subscribe(cb);
        this.blockModelUpdate$.subscribe(cb);
        this.blockModelAttached$.subscribe(cb);
        this.blockModelDetached$.subscribe(cb);
        this.blockModelDestroy$.subscribe(cb);
        this.blockModelStructUpdate$.subscribe(cb);
    }
    run(blocks) {
        blocks.forEach(({
            block,
            action
        }) => {
            this.process(block, action);
        });
    }
    process(block, action) {
        switch (action) {
            case 'added':
                this.blockModelCreate$.next({
                    block
                });
                block.attach$.next({});
                this.blockModelAttached$.next({
                    block
                });
                block.structUpdate$.next({
                    block
                });
                this.blockModelStructUpdate$.next({
                    block
                });
                this.blockModelUpdate$.next({
                    block
                });
                break;
            case 'removed':
                block.detach$.next({});
                this.blockModelDetached$.next({
                    block
                });
                block.destroy$.next({});
                this.blockModelDestroy$.next({
                    block
                });
                block.structUpdate$.next({
                    block
                });
                this.blockModelStructUpdate$.next({
                    block
                });
                this.blockModelUpdate$.next({
                    block
                });
                break;
            case 'updated':
                block.update$.next({});
                this.blockModelUpdate$.next({
                    block
                });
                break;
                // case 'moved':
                //   block.detach$.next({});
                //   this.blockModelDetached$.next({ block });
                //   block.destroy$.next({});
                //   this.blockModelDestroy$.next({ block });
                //   this.blockModelCreate$.next({ block });
                //   block.attach$.next({});
                //   this.blockModelAttached$.next({ block });
                //   block.structUpdate$.next({ block });
                //   this.blockModelStructUpdate$.next({ block });
                //   this.blockModelUpdate$.next({ block });
                //   break;
        }
    }
}
function range(start, end) {
    const xs = [];
    for (let i = start; i < end; ++i) {
        xs[i] = start + i;
    }
    return xs;
}
const counts = [20000];
const actions = ['added', 'removed', 'updated'];
const types = range(0, 52).map(i => String.fromCharCode('A'.charCodeAt(0) + i));
const testIds = counts.map(count => range(0, count));
const testBlocks = testIds.map(is => is.map(id => ({
    id,
    type: types[id % types.length]
})));
const testActions = testIds.map(is => is.map(i => actions[i % actions.length]));
const testListeners = testBlocks.map(blocks => [...blocks, ...blocks].map((block, i) => {
    return {
        callback: () => {},
        condition: Math.random() < 0.5 ? null : {
            type: Math.random() < 0.5 ? null : types[i % types.length],
            id: Math.random() < 0.5 ? null : block.id,
        },
    };
}));
const testSubjects = testListeners.map(listeners => {
    const subject = new Subject();
    listeners.forEach(({
        callback
    }) => {
        subject.subscribe(callback);
    });
    return subject;
});
const testBlockModels = testIds.map((is, i) => is.map((id, j) => {
    const block = new BlockModel(id, types[id % types.length]);
    return {
        block,
        action: testActions[i][j],
    }
}));
testForAdd = (index, mutations) => {
    const blocks = testBlocks[index];
    const actions = testActions[index];
    blocks.forEach((block, i) => {
        mutations.add({
            action: actions[i],
            block
        });
    });
};
testForDispatch = (index, mutations) => {
    const listeners = testListeners[index];
    mutations.listeners = listeners;
    mutations.dispatch(listeners);
};
testForSubjects = (index, mutations) => {
    const subject = testSubjects[index];
    mutations.finalize();
    subject.next(mutations);
};
testForBlockModelSubject = index => {
    const blocks = testBlockModels[index];
    blockManager.run(blocks);
};
Tests:
  • bm1

     
    mutations = new BlockMutations2();
    testForAdd(0, mutations);
    testForDispatch(0, mutations);
  • bm2

     
    mutations = new BlockMutations(true);
    testForAdd(0, mutations);
    testForDispatch(0, mutations);
  • bm3

     
    mutations = new BlockMutations(false);
    testForAdd(0, mutations);
    testForDispatch(0, mutations);
  • bm4

     
    testForBlockModelSubject(0);
  • bm5

     
    mutations = new BlockMutations(true);
    testForAdd(0, mutations);
    testForSubjects(0, mutations);
  • bm6

     
    mutations = new BlockMutations2();
    testForAdd(0, mutations);
    testForSubjects(0, mutations);
  • bm7

     
    mutations = new BlockMutations(false);
    testForAdd(0, mutations);
    testForSubjects(0, mutations);
Rendered benchmark preparation results:

Suite status: <idle, ready to run>

Previous results

Experimental features:

  • Test case name Result
    bm1
    bm2
    bm3
    bm4
    bm5
    bm6
    bm7

    Fastest: N/A

    Slowest: N/A

Latest run results:
Run details: (Test run date: 8 months ago)
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Chrome 127 on Mac OS X 10.15.7
View result in a separate tab
Test name Executions per second
bm1 581.7 Ops/sec
bm2 653.4 Ops/sec
bm3 607.9 Ops/sec
bm4 434.5 Ops/sec
bm5 548.3 Ops/sec
bm6 490.0 Ops/sec
bm7 503.0 Ops/sec