@lightningjs/blits 1.7.1 → 1.8.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.7.1",
3
+ "version": "1.8.1",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -13,12 +13,13 @@
13
13
  },
14
14
  "scripts": {
15
15
  "test": "c8 npm run test:run",
16
- "test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules)/**/}*.test.js' | tap-diff",
16
+ "test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-diff",
17
17
  "lint": "eslint '**/*.js'",
18
18
  "lint:fix": "eslint '**/*.js' --fix",
19
19
  "prepublishOnly": "node scripts/prepublishOnly.js",
20
20
  "postpublish": "node scripts/postpublish.js"
21
21
  },
22
+ "types": "./index.d.ts",
22
23
  "lint-staged": {
23
24
  "*.js": [
24
25
  "eslint --fix"
@@ -47,8 +48,8 @@
47
48
  "tape": "^5.5.0"
48
49
  },
49
50
  "dependencies": {
50
- "@lightningjs/msdf-generator": "^1.0.2",
51
- "@lightningjs/renderer": "^2.3.2"
51
+ "@lightningjs/msdf-generator": "^1.1.0",
52
+ "@lightningjs/renderer": "^2.5.1"
52
53
  },
53
54
  "repository": {
54
55
  "type": "git",
@@ -141,6 +141,7 @@ export default () =>
141
141
  props7['h'] = 20
142
142
  props7['w'] = 20
143
143
  props7['map'] = component.sprite
144
+
144
145
  props7['frame'] = component.fps[0]
145
146
 
146
147
  componentType = props7['is'] || 'Sprite'
@@ -198,6 +199,7 @@ export default () =>
198
199
  props9['h'] = 20
199
200
  props9['w'] = 20
200
201
  props9['map'] = component.sprite
202
+
201
203
  props9['frame'] = component.fps[1]
202
204
 
203
205
  componentType = props9['is'] || 'Sprite'
@@ -255,6 +257,7 @@ export default () =>
255
257
  props11['h'] = 20
256
258
  props11['w'] = 20
257
259
  props11['map'] = component.sprite
260
+
258
261
  props11['frame'] = component.fps[2]
259
262
 
260
263
  componentType = props11['is'] || 'Sprite'
@@ -386,6 +389,7 @@ export default () =>
386
389
  props17['h'] = 20
387
390
  props17['w'] = 20
388
391
  props17['map'] = component.sprite
392
+
389
393
  props17['frame'] = component.avgFps[0]
390
394
 
391
395
  componentType = props17['is'] || 'Sprite'
@@ -443,6 +447,7 @@ export default () =>
443
447
  props19['h'] = 20
444
448
  props19['w'] = 20
445
449
  props19['map'] = component.sprite
450
+
446
451
  props19['frame'] = component.avgFps[1]
447
452
 
448
453
  componentType = props19['is'] || 'Sprite'
@@ -500,6 +505,7 @@ export default () =>
500
505
  props21['h'] = 20
501
506
  props21['w'] = 20
502
507
  props21['map'] = component.sprite
508
+
503
509
  props21['frame'] = component.avgFps[2]
504
510
 
505
511
  componentType = props21['is'] || 'Sprite'
@@ -632,6 +638,7 @@ export default () =>
632
638
  props27['h'] = 20
633
639
  props27['w'] = 20
634
640
  props27['map'] = component.sprite
641
+
635
642
  props27['frame'] = component.minFps[0]
636
643
 
637
644
  componentType = props27['is'] || 'Sprite'
@@ -689,6 +696,7 @@ export default () =>
689
696
  props29['h'] = 20
690
697
  props29['w'] = 20
691
698
  props29['map'] = component.sprite
699
+
692
700
  props29['frame'] = component.minFps[1]
693
701
 
694
702
  componentType = props29['is'] || 'Sprite'
@@ -746,6 +754,7 @@ export default () =>
746
754
  props31['h'] = 20
747
755
  props31['w'] = 20
748
756
  props31['map'] = component.sprite
757
+
749
758
  props31['frame'] = component.minFps[2]
750
759
 
751
760
  componentType = props31['is'] || 'Sprite'
@@ -876,6 +885,7 @@ export default () =>
876
885
  props37['h'] = 20
877
886
  props37['w'] = 20
878
887
  props37['map'] = component.sprite
888
+
879
889
  props37['frame'] = component.maxFps[0]
880
890
 
881
891
  componentType = props37['is'] || 'Sprite'
@@ -933,6 +943,7 @@ export default () =>
933
943
  props39['h'] = 20
934
944
  props39['w'] = 20
935
945
  props39['map'] = component.sprite
946
+
936
947
  props39['frame'] = component.maxFps[1]
937
948
 
938
949
  componentType = props39['is'] || 'Sprite'
@@ -990,6 +1001,7 @@ export default () =>
990
1001
  props41['h'] = 20
991
1002
  props41['w'] = 20
992
1003
  props41['map'] = component.sprite
1004
+
993
1005
  props41['frame'] = component.maxFps[2]
994
1006
 
995
1007
  componentType = props41['is'] || 'Sprite'
@@ -386,13 +386,21 @@ const Element = {
386
386
  f,
387
387
  }
388
388
 
389
- if (transition.start && typeof transition.start === 'function') {
389
+ if (transition.start !== undefined && typeof transition.start === 'function') {
390
390
  // fire transition start callback when animation really starts (depending on specified delay)
391
391
  f.once('animating', () => {
392
392
  transition.start.call(this.component, this, prop, startValue)
393
393
  })
394
394
  }
395
395
 
396
+ if (transition.progress !== undefined && typeof transition.progress === 'function') {
397
+ let prevProgress = 0
398
+ f.on('tick', (_node, { progress }) => {
399
+ transition.progress.call(this.component, this, prop, progress, prevProgress)
400
+ prevProgress = progress
401
+ })
402
+ }
403
+
396
404
  f.once('stopped', () => {
397
405
  // remove the prop from scheduled transitions
398
406
  this.scheduledTransitions[prop] = undefined
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright 2024 Comcast Cable Communications Management, LLC
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
+ * SPDX-License-Identifier: Apache-2.0
16
+ */
17
+
18
+ const deepEqualArray = (array1, array2) => {
19
+ if (array1 === array2) return true
20
+ if (array1.length !== array2.length) return false
21
+
22
+ let l = array1.length
23
+
24
+ while (l--) {
25
+ const value1 = array1[l]
26
+ const value2 = array2[l]
27
+
28
+ // values are arrays, deepEqual nested arrays
29
+ if (Array.isArray(value1) && Array.isArray(value2)) {
30
+ if (deepEqualArray(value1, value2) === false) return false
31
+ }
32
+ // values are objects, deepEqual nested objects
33
+ else if (
34
+ typeof value1 === 'object' &&
35
+ value1 !== null &&
36
+ typeof value2 === 'object' &&
37
+ value2 !== null
38
+ ) {
39
+ if (deepEqualArray(Object.entries(value1), Object.entries(value2)) === false) return false
40
+ }
41
+ // other type, do strict equal check
42
+ else if (value1 !== value2) {
43
+ return false
44
+ }
45
+ }
46
+
47
+ return true
48
+ }
49
+
50
+ export default deepEqualArray
@@ -0,0 +1,127 @@
1
+ /*
2
+ * Copyright 2024 Comcast Cable Communications Management, LLC
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
+ * SPDX-License-Identifier: Apache-2.0
16
+ */
17
+
18
+ import test from 'tape'
19
+ import deepEqual from './deepEqualArray.js'
20
+
21
+ test('Type', (assert) => {
22
+ const expected = 'function'
23
+ const actual = typeof deepEqual
24
+
25
+ assert.equal(actual, expected, 'deepEqual should be a function')
26
+ assert.end()
27
+ })
28
+
29
+ test('The same arrays', (assert) => {
30
+ const val1 = ['hello', 'world']
31
+
32
+ assert.true(
33
+ deepEqual(val1, val1),
34
+ 'deepEqual should return true if the values are the same reference'
35
+ )
36
+ assert.end()
37
+ })
38
+
39
+ test('Equal simple arrays', (assert) => {
40
+ const val1 = ['hello', 'world']
41
+ const val2 = ['hello', 'world']
42
+
43
+ assert.true(deepEqual(val1, val2), 'deepEqual should return true if simple arrays are equal')
44
+ assert.end()
45
+ })
46
+
47
+ test('Not equal simple arrays', (assert) => {
48
+ const val1 = ['hello', 'world']
49
+ const val2 = ['world', 'hello']
50
+
51
+ assert.false(
52
+ deepEqual(val1, val2),
53
+ 'deepEqual should return false if simple arrays are not equal'
54
+ )
55
+ assert.end()
56
+ })
57
+
58
+ test('Not Equal simple arrays (different length)', (assert) => {
59
+ const val1 = ['hello', 'world']
60
+ const val2 = ['hello', 'world', '!']
61
+
62
+ assert.false(deepEqual(val1, val2), 'deepEqual should return false if simple arrays are equal')
63
+ assert.end()
64
+ })
65
+
66
+ test('Equal nested arrays', (assert) => {
67
+ const val1 = ['hello', ['world', '!']]
68
+ const val2 = ['hello', ['world', '!']]
69
+
70
+ assert.true(deepEqual(val1, val2), 'deepEqual should return true if nested arrays are equal')
71
+ assert.end()
72
+ })
73
+
74
+ test('Not equal nested arrays', (assert) => {
75
+ const val1 = ['hello', ['world', '!']]
76
+ const val2 = ['hello', ['world', '!', '?']]
77
+
78
+ assert.false(
79
+ deepEqual(val1, val2),
80
+ 'deepEqual should return false if nested arrays are not equal'
81
+ )
82
+ assert.end()
83
+ })
84
+
85
+ test('Equal arrays with objects', (assert) => {
86
+ const val1 = [{ foo: 'bar', bar: 'foo' }, { hello: 'world' }]
87
+ const val2 = [{ foo: 'bar', bar: 'foo' }, { hello: 'world' }]
88
+
89
+ assert.true(
90
+ deepEqual(val1, val2),
91
+ 'deepEqual should return true if arrays with objects are equal'
92
+ )
93
+ assert.end()
94
+ })
95
+
96
+ test('Not equal arrays with objects (keys', (assert) => {
97
+ const val1 = [{ foo: 'bar', bar: 'foo' }, { test: 'bla' }]
98
+ const val2 = [{ foo2: 'bar', bar2: 'foo' }, { test: 'bla' }]
99
+
100
+ assert.false(
101
+ deepEqual(val1, val2),
102
+ 'deepEqual should return false if arrays with objects are not equal'
103
+ )
104
+ assert.end()
105
+ })
106
+
107
+ test('Equal arrays with mixed values', (assert) => {
108
+ const val1 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 2 }]]]
109
+ const val2 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 2 }]]]
110
+
111
+ assert.true(
112
+ deepEqual(val1, val2),
113
+ 'deepEqual should return true if arrays with mixed values are equal'
114
+ )
115
+ assert.end()
116
+ })
117
+
118
+ test('Not Equal arrays with mixed values', (assert) => {
119
+ const val1 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 2 }]]]
120
+ const val2 = ['foo', { foo: 'bar', bar: 'foo' }, ['hello', 'world', ['this', 'is', { level: 3 }]]]
121
+
122
+ assert.false(
123
+ deepEqual(val1, val2),
124
+ 'deepEqual should return true if arrays with mixed values are not equal'
125
+ )
126
+ assert.end()
127
+ })
@@ -212,12 +212,11 @@ const generateComponentCode = function (
212
212
  templateObject[key],
213
213
  options.component
214
214
  )}`)
215
- renderCode.push(
216
- `props${counter}['${key.substring(1)}'] = ${interpolate(
217
- templateObject[key],
218
- options.component
219
- )}`
220
- )
215
+ renderCode.push(`
216
+ props${counter}['${key.substring(1)}']= ${interpolate(
217
+ templateObject[key],
218
+ options.component
219
+ )}`)
221
220
  } else {
222
221
  renderCode.push(
223
222
  `props${counter}['${key}'] = ${cast(templateObject[key], key, options.component)}`
@@ -18,6 +18,7 @@
18
18
  import { ImageTexture } from '@lightningjs/renderer'
19
19
  import { track, trigger, pauseTracking, resumeTracking } from './effect.js'
20
20
  import symbols from '../symbols.js'
21
+ import deepEqualArray from '../../helpers/deepEqualArray.js'
21
22
 
22
23
  const arrayPatchMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort']
23
24
 
@@ -38,7 +39,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
38
39
 
39
40
  // if original object is already a proxy, don't create a new one but return the existing one instead
40
41
  const existingProxy = proxyMap.get(original)
41
- if (existingProxy) {
42
+ if (existingProxy !== undefined) {
42
43
  return existingProxy
43
44
  }
44
45
 
@@ -50,9 +51,9 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
50
51
  }
51
52
 
52
53
  // handling arrays
53
- if (Array.isArray(target)) {
54
+ if (Array.isArray(target) === true) {
54
55
  if (typeof target[key] === 'object' && target[key] !== null) {
55
- if (Array.isArray(target[key])) {
56
+ if (Array.isArray(target[key]) === true) {
56
57
  track(target, key, global)
57
58
  }
58
59
  // create a new reactive proxy
@@ -80,7 +81,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
80
81
 
81
82
  // handling objects (but not null values, which have object type in JS)
82
83
  if (typeof target[key] === 'object' && target[key] !== null) {
83
- if (Array.isArray(target[key])) {
84
+ if (Array.isArray(target[key]) === true) {
84
85
  track(target, key, global)
85
86
  }
86
87
  // create a new reactive proxy
@@ -99,13 +100,19 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
99
100
  const rawValue = getRaw(value)
100
101
 
101
102
  let result = true
102
- if (oldRawValue !== rawValue) {
103
+ const isEqual =
104
+ Array.isArray(rawValue) === true
105
+ ? deepEqualArray(oldRawValue, rawValue)
106
+ : oldRawValue === rawValue
107
+
108
+ if (isEqual === false) {
109
+ if (Array.isArray(value) === true) value = getRaw(value).slice(0)
103
110
  result = Reflect.set(target, key, value, receiver)
104
111
  }
105
112
 
106
- if (result && oldRawValue !== rawValue) {
113
+ if (result === true && isEqual === false) {
107
114
  // if we're assigning an array key directly trigger reactivity on the parent key as well
108
- if (Array.isArray(target) && key in target) {
115
+ if (Array.isArray(target) === true && key in target === true) {
109
116
  trigger(_parent, _key, true)
110
117
  }
111
118
  trigger(target, key, true)
@@ -126,7 +133,7 @@ const reactiveDefineProperty = (target, global) => {
126
133
  if (target[key] !== null && typeof target[key] === 'object') {
127
134
  if (Object.getPrototypeOf(target[key]) === Object.prototype) {
128
135
  return reactiveDefineProperty(target[key])
129
- } else if (Array.isArray(target[key])) {
136
+ } else if (Array.isArray(target[key]) === true) {
130
137
  for (let i = 0; i < arrayPatchMethods.length - 1; i++) {
131
138
  target[key][arrayPatchMethods[i]] = function (v) {
132
139
  Array.prototype[arrayPatchMethods[i]].call(this, v)
@@ -1,5 +1,6 @@
1
1
  import path from 'path'
2
2
  import * as fs from 'fs'
3
+ import { createHash } from 'crypto'
3
4
  import { genFont, setGeneratePaths } from '@lightningjs/msdf-generator'
4
5
  import { adjustFont } from '@lightningjs/msdf-generator/adjustFont'
5
6
 
@@ -21,6 +22,8 @@ class TaskQueue {
21
22
 
22
23
  const fontGenerationQueue = new TaskQueue()
23
24
  let config
25
+ let checksumPaths = []
26
+ let checksumData = []
24
27
 
25
28
  export default function () {
26
29
  let msdfOutputDir = ''
@@ -53,10 +56,12 @@ export default function () {
53
56
  // Attempt to find the font file with supported extensions
54
57
  const supportedExtensions = ['ttf', 'otf', 'woff']
55
58
  let fontFile = null
59
+ let fontType = null
56
60
  for (const fontExt of supportedExtensions) {
57
61
  const potentialPath = path.join(fontDir, `${fontName}.${fontExt}`)
58
62
  if (fs.existsSync(potentialPath)) {
59
63
  fontFile = potentialPath
64
+ fontType = '.' + fontExt
60
65
  break
61
66
  }
62
67
  }
@@ -70,10 +75,13 @@ export default function () {
70
75
  const mimeType = ext === 'png' ? 'image/png' : 'application/json'
71
76
 
72
77
  await fontGenerationQueue.enqueue(async () => {
73
- if (!fs.existsSync(generatedFontFile)) {
74
- // Check if generation is needed
78
+ if (isGenerationRequired(fontDir, targetDir, fontName, fontType)) {
79
+ const configFilePath = path.join(fontDir, `${fontName}.config.json`)
80
+
75
81
  console.log(`\nGenerating ${targetDir}/${fontName}.msdf.${ext}`)
76
- await generateSDF(fontFile, path.join(targetDir))
82
+ await generateSDF(fontFile, path.join(targetDir), configFilePath)
83
+
84
+ saveChecksum()
77
85
  }
78
86
  })
79
87
 
@@ -109,14 +117,17 @@ export default function () {
109
117
 
110
118
  for (const fontFile of fontFiles) {
111
119
  const relativePath = path.relative(publicDir, path.dirname(fontFile))
120
+ const fontExt = path.extname(fontFile)
112
121
  const baseName = path.basename(fontFile).replace(/\.(ttf|otf|woff)$/i, '')
113
- const msdfJsonPath = path.join(msdfOutputDir, relativePath, `${baseName}.msdf.json`)
114
- const msdfPngPath = path.join(msdfOutputDir, relativePath, `${baseName}.msdf.png`)
122
+ const targetDir = path.join(msdfOutputDir, relativePath)
115
123
 
116
- // Check if MSDF files are generated, if not, generate them
117
- if (!fs.existsSync(msdfJsonPath) || !fs.existsSync(msdfPngPath)) {
124
+ // Check MSDF generation is required
125
+ if (isGenerationRequired(path.dirname(fontFile), targetDir, baseName, fontExt)) {
126
+ const configFilePath = path.join(path.dirname(fontFile), `${baseName}.config.json`)
118
127
  console.log(`Generating missing MSDF files for ${fontFile}`)
119
- await generateSDF(fontFile, path.join(msdfOutputDir, relativePath))
128
+ await generateSDF(fontFile, path.join(msdfOutputDir, relativePath), configFilePath)
129
+
130
+ saveChecksum()
120
131
  }
121
132
  }
122
133
 
@@ -127,11 +138,11 @@ export default function () {
127
138
  }
128
139
  }
129
140
 
130
- const generateSDF = async (inputFilePath, outputDirPath) => {
141
+ const generateSDF = async (inputFilePath, outputDirPath, configFilePath) => {
131
142
  // Ensure the destination directory exists
132
143
  fs.mkdirSync(outputDirPath, { recursive: true })
133
144
 
134
- setGeneratePaths(path.dirname(inputFilePath), outputDirPath)
145
+ setGeneratePaths(path.dirname(inputFilePath), outputDirPath, configFilePath)
135
146
 
136
147
  let font = await genFont(path.basename(inputFilePath), 'msdf')
137
148
 
@@ -168,6 +179,74 @@ const copyDir = (src, dest) => {
168
179
  const srcPath = path.join(src, entry.name)
169
180
  const destPath = path.join(dest, entry.name)
170
181
 
182
+ // Should not copy .checksum files to dist directory
183
+ if (srcPath.indexOf('.checksum') !== -1) continue
184
+
171
185
  entry.isDirectory() ? copyDir(srcPath, destPath) : fs.copyFileSync(srcPath, destPath) // Copy files
172
186
  }
173
187
  }
188
+
189
+ // Check font config.json or font file modified since last generation
190
+ const isGenerationRequired = (fontDir, targetDir, fontName, fontExt) => {
191
+ const fontFilePath = path.resolve(fontDir, fontName + fontExt)
192
+ const fontChecksumPath = path.resolve(targetDir, fontName + fontExt + '.checksum')
193
+
194
+ const configJsonPath = path.resolve(fontDir, fontName + '.config.json')
195
+ const configChecksumPath = path.resolve(targetDir, fontName + '.config.checksum')
196
+
197
+ // If font file checksum not exists, should generate msdf
198
+ if (!fs.existsSync(fontChecksumPath)) {
199
+ checksumPaths.push(fontChecksumPath)
200
+ checksumData.push(generateHash(fontFilePath))
201
+
202
+ if (fs.existsSync(configJsonPath)) {
203
+ checksumPaths.push(configChecksumPath)
204
+ checksumData.push(generateHash(configJsonPath))
205
+ }
206
+ return true
207
+ }
208
+
209
+ // If config.json exists, can be newly added or modified since last generation of msdf
210
+ if (fs.existsSync(configJsonPath)) {
211
+ // If config.json checksum not exists, should generate msdf
212
+ if (!fs.existsSync(configChecksumPath)) {
213
+ checksumPaths.push(configChecksumPath)
214
+ checksumData.push(generateHash(configJsonPath))
215
+ return true
216
+ }
217
+ const isConfigModified = isInputFileModified(configJsonPath, configChecksumPath)
218
+
219
+ // If config.json is modified, should generate msdf
220
+ if (isConfigModified) return true
221
+ }
222
+
223
+ // Check font file modified, if modified, should generate msdf
224
+ return isInputFileModified(fontFilePath, fontChecksumPath)
225
+ }
226
+
227
+ const isInputFileModified = (inputFilePath, targetFilePath) => {
228
+ const inputChecksum = generateHash(inputFilePath)
229
+ const targetChecksum = fs.readFileSync(targetFilePath).toString()
230
+ if (inputChecksum !== targetChecksum) {
231
+ checksumPaths.push(targetFilePath)
232
+ checksumData.push(inputChecksum)
233
+ return true
234
+ }
235
+ return false
236
+ }
237
+
238
+ const generateHash = (filePath) => {
239
+ const buffer = fs.readFileSync(filePath)
240
+ return createHash('md5').update(buffer).digest('hex')
241
+ }
242
+
243
+ const saveChecksum = () => {
244
+ if (checksumPaths.length > 0 && checksumData.length > 0) {
245
+ for (let i = 0; i < checksumPaths.length; i++) {
246
+ // Save checksum
247
+ fs.writeFileSync(checksumPaths[i], checksumData[i])
248
+ }
249
+ checksumData.length = 0
250
+ checksumPaths.length = 0
251
+ }
252
+ }