1/* 2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { AtomicRef } from "@koalaui/compat" 17 18/** 19 * A markable queue that allows to accumulate callbacks and to call them to the latest set marker. 20 */ 21export interface MarkableQueue { 22 /** Sets the new marker to the queue. */ 23 setMarker(): void 24 /** Adds the given callback to the queue. */ 25 addCallback(callback: () => void): void 26 /** Calls all accumulated callbacks to the latest set marker. */ 27 callCallbacks(): void 28 /** Clears the queue. */ 29 clear(): void 30} 31 32/** 33 * Creates a new markable queue to safely process callbacks across several threads or tasks. 34 * @param reversed - `true` changes the order of calling callbacks 35 */ 36export function markableQueue(reversed: boolean = false): MarkableQueue { 37 return reversed ? new ReversedQueue() : new DefaultQueue() 38} 39 40class DefaultQueue implements MarkableQueue { 41 private readonly last = new AtomicRef<Block>(new Block()) 42 private readonly first = new AtomicRef<Block>(this.last.value) 43 private readonly marker = new AtomicRef<Block | undefined>(undefined) 44 45 setMarker(): void { 46 const marker = new Block() 47 this.last.getAndSet(marker).next.value = marker 48 this.marker.value = marker 49 } 50 51 addCallback(callback: () => void): void { 52 const block = new Block(callback) 53 this.last.getAndSet(block).next.value = block 54 } 55 56 callCallbacks(): void { 57 const marker = this.marker.getAndSet(undefined) 58 if (marker) { 59 let block = this.first.getAndSet(marker) 60 while (block !== marker) { 61 block.callback?.() 62 block = block.next.value! 63 } 64 } 65 } 66 67 clear(): void { 68 this.last.value = this.first.value 69 this.marker.value = undefined 70 } 71} 72 73class ReversedQueue implements MarkableQueue { 74 private readonly last = new AtomicRef<Block | undefined>(undefined) 75 private readonly marker = new AtomicRef<Block | undefined>(undefined) 76 77 setMarker(): void { 78 const marker = new Block() 79 marker.next.value = this.last.getAndSet(marker) 80 this.marker.value = marker 81 } 82 83 addCallback(callback: () => void): void { 84 const block = new Block(callback) 85 block.next.value = this.last.getAndSet(block) 86 } 87 88 callCallbacks(): void { 89 const marker = this.marker.getAndSet(undefined) 90 if (marker) { 91 let block = marker.next.getAndSet(undefined) 92 while (block) { 93 block!.callback?.() 94 block = block!.next.value 95 } 96 } 97 } 98 99 clear(): void { 100 this.last.value = undefined 101 this.marker.value = undefined 102 } 103} 104 105class Block { 106 readonly next = new AtomicRef<Block | undefined>(undefined) 107 readonly callback: (() => void) | undefined 108 109 constructor(callback?: () => void) { 110 this.callback = callback 111 } 112} 113