pixel-data-js 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.prod.cjs +69 -7
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +63 -5
- package/dist/index.prod.js +67 -7
- package/dist/index.prod.js.map +1 -1
- package/package.json +1 -1
- package/src/Control/BatchedQueue.ts +76 -0
- package/src/Control/RenderQueue.ts +49 -0
- package/src/History/PixelWriter.ts +5 -9
- package/src/index.ts +3 -0
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type BatchedQueueFn = (fn: () => void) => void
|
|
2
|
+
|
|
3
|
+
export type BatchedQueue = ReturnType<typeof makeBatchedQueue>
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a high-performance, zero-allocation batching queue.
|
|
7
|
+
* This utility collects items marked as "dirty" and flushes them in a single batch.
|
|
8
|
+
* * **⚠️ CRITICAL: Synchronous Processing Required**
|
|
9
|
+
* Because the internal sets are reused, the `Set` passed to the `processor` is instantly
|
|
10
|
+
* cleared the moment the processor function returns. If you need to process the items
|
|
11
|
+
* asynchronously, you **must** manually clone the set inside your processor.
|
|
12
|
+
* @template T - The type of items being batched.
|
|
13
|
+
* @param processor - The callback executed when the batch flushes. Receives a `Set` of all batched items.
|
|
14
|
+
* @param queue - The scheduling function used to defer the flush. Defaults to `queueMicrotask`.
|
|
15
|
+
* @returns An object containing methods to mark items as dirty.
|
|
16
|
+
* @example
|
|
17
|
+
* * @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { nextTick } from 'vue'
|
|
20
|
+
* let bq = makeBatchedQueue<string>(
|
|
21
|
+
* (items) => drawSomething(items),
|
|
22
|
+
* nextTick,
|
|
23
|
+
* )
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function makeBatchedQueue<T>(
|
|
27
|
+
processor: (items: Set<T>) => void,
|
|
28
|
+
queue: BatchedQueueFn,
|
|
29
|
+
) {
|
|
30
|
+
let activeSet = new Set<T>()
|
|
31
|
+
let processingSet = new Set<T>()
|
|
32
|
+
let scheduled = false
|
|
33
|
+
|
|
34
|
+
const flush = () => {
|
|
35
|
+
// swap sets
|
|
36
|
+
const current = activeSet
|
|
37
|
+
activeSet = processingSet
|
|
38
|
+
processingSet = current
|
|
39
|
+
|
|
40
|
+
scheduled = false
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
processor(processingSet)
|
|
44
|
+
} finally {
|
|
45
|
+
processingSet.clear()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function markDirty(item: T) {
|
|
50
|
+
activeSet.add(item)
|
|
51
|
+
|
|
52
|
+
if (!scheduled) {
|
|
53
|
+
scheduled = true
|
|
54
|
+
queue(flush)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function markMultipleDirty(items: T[]) {
|
|
59
|
+
let len = items.length
|
|
60
|
+
if (len === 0) return
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < len; i++) {
|
|
63
|
+
activeSet.add(items[i])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!scheduled) {
|
|
67
|
+
scheduled = true
|
|
68
|
+
queue(flush)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
markDirty,
|
|
74
|
+
markMultipleDirty,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a debounced render queue using `requestAnimationFrame`.
|
|
3
|
+
* This utility ensures that a callback is executed exactly once right before
|
|
4
|
+
* the next visual frame, regardless of how many times the trigger is called
|
|
5
|
+
* synchronously. It safely prevents layout thrashing and redundant computations.
|
|
6
|
+
* @param cb - The function to execute on the next animation frame.
|
|
7
|
+
* @returns A trigger function that schedules the callback. It includes a `.cancel()` method to abort the pending frame.
|
|
8
|
+
* * @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* let renderQueue = makeRenderQueue(() => {
|
|
11
|
+
* console.log('DOM updated!')
|
|
12
|
+
* })
|
|
13
|
+
* * // Calling this multiple times synchronously...
|
|
14
|
+
* renderQueue()
|
|
15
|
+
* renderQueue()
|
|
16
|
+
* renderQueue()
|
|
17
|
+
* * // ...will only result in one 'DOM updated!' log on the next frame.
|
|
18
|
+
* ```
|
|
19
|
+
* * @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* // Canceling a scheduled render (e.g., when a component unmounts)
|
|
22
|
+
* let trigger = makeRenderQueue(updateLayout)
|
|
23
|
+
* trigger()
|
|
24
|
+
* trigger.cancel() // The callback will not execute
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function makeRenderQueue(cb: () => void) {
|
|
28
|
+
let needsRender = false
|
|
29
|
+
let frameId = 0
|
|
30
|
+
|
|
31
|
+
const trigger = () => {
|
|
32
|
+
if (needsRender) return
|
|
33
|
+
needsRender = true
|
|
34
|
+
|
|
35
|
+
frameId = requestAnimationFrame(() => {
|
|
36
|
+
needsRender = false
|
|
37
|
+
cb()
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
trigger.cancel = () => {
|
|
42
|
+
if (needsRender) {
|
|
43
|
+
cancelAnimationFrame(frameId)
|
|
44
|
+
needsRender = false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return trigger
|
|
49
|
+
}
|
|
@@ -8,6 +8,7 @@ import { type HistoryActionFactory, makeHistoryAction } from './HistoryAction'
|
|
|
8
8
|
import { HistoryManager } from './HistoryManager'
|
|
9
9
|
import { PixelAccumulator } from './PixelAccumulator'
|
|
10
10
|
import { PixelEngineConfig } from './PixelEngineConfig'
|
|
11
|
+
import type { PixelPatchTiles } from './PixelPatchTiles'
|
|
11
12
|
|
|
12
13
|
export interface PixelWriterOptions {
|
|
13
14
|
maxHistorySteps?: number
|
|
@@ -72,15 +73,13 @@ export class PixelWriter<M> {
|
|
|
72
73
|
* throw immediately to prevent silent data loss from a nested extractPatch.
|
|
73
74
|
*
|
|
74
75
|
* @param transaction Callback to be executed inside the transaction.
|
|
75
|
-
* @param
|
|
76
|
-
* @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
|
|
76
|
+
* @param afterUndo Called after undo only.
|
|
77
77
|
* @param afterRedo Called after redo only.
|
|
78
78
|
*/
|
|
79
79
|
withHistory(
|
|
80
80
|
transaction: (mutator: M) => void,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
afterRedo?: () => void,
|
|
81
|
+
afterUndo?: (patch: PixelPatchTiles) => void,
|
|
82
|
+
afterRedo?: (patch: PixelPatchTiles) => void,
|
|
84
83
|
): void {
|
|
85
84
|
if (this._inProgress) {
|
|
86
85
|
throw new Error('withHistory is not re-entrant — commit or rollback the current operation first')
|
|
@@ -100,7 +99,7 @@ export class PixelWriter<M> {
|
|
|
100
99
|
if (this.accumulator.beforeTiles.length === 0) return
|
|
101
100
|
|
|
102
101
|
const patch = this.accumulator.extractPatch()
|
|
103
|
-
const action = this.historyActionFactory(this.config, this.accumulator, patch,
|
|
102
|
+
const action = this.historyActionFactory(this.config, this.accumulator, patch, afterUndo, afterRedo)
|
|
104
103
|
|
|
105
104
|
this.historyManager.commit(action)
|
|
106
105
|
}
|
|
@@ -110,7 +109,6 @@ export class PixelWriter<M> {
|
|
|
110
109
|
newHeight: number,
|
|
111
110
|
offsetX = 0,
|
|
112
111
|
offsetY = 0,
|
|
113
|
-
after?: (target: ImageData) => void,
|
|
114
112
|
afterUndo?: (target: ImageData) => void,
|
|
115
113
|
afterRedo?: (target: ImageData) => void,
|
|
116
114
|
resizeImageDataFn = resizeImageData,
|
|
@@ -134,12 +132,10 @@ export class PixelWriter<M> {
|
|
|
134
132
|
undo: () => {
|
|
135
133
|
setPixelData(target, beforeImageData)
|
|
136
134
|
afterUndo?.(beforeImageData)
|
|
137
|
-
after?.(beforeImageData)
|
|
138
135
|
},
|
|
139
136
|
redo: () => {
|
|
140
137
|
setPixelData(target, afterImageData)
|
|
141
138
|
afterRedo?.(afterImageData)
|
|
142
|
-
after?.(afterImageData)
|
|
143
139
|
},
|
|
144
140
|
})
|
|
145
141
|
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,9 @@ export * from './Clipboard/writeImgBlobToClipboard'
|
|
|
26
26
|
|
|
27
27
|
export * from './color'
|
|
28
28
|
|
|
29
|
+
export * from './Control/BatchedQueue'
|
|
30
|
+
export * from './Control/RenderQueue'
|
|
31
|
+
|
|
29
32
|
export * from './History/HistoryAction'
|
|
30
33
|
export * from './History/HistoryManager'
|
|
31
34
|
export * from './History/PixelAccumulator'
|