Add .fig test fixtures (LFS) and fix O(n²) import bottleneck
- Add Material 3 Design Kit and Nuxt UI v4 community files as test fixtures - Track tests/fixtures/*.fig with Git LFS - Fix O(n²) getChildren() in fig-import: build children index upfront - material3.fig (87K nodes): 37s → 535ms (69x faster) - nuxtui.fig (314K nodes): minutes → 2.3s - Optimize kiwi ByteBuffer: inline readVarUint, use TextDecoder for strings
This commit is contained in:
parent
88bfacdc9e
commit
f24f6e0dc6
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
tests/fixtures/*.fig filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
@ -374,6 +374,7 @@ export function importNodeChanges(
|
|||
|
||||
const changeMap = new Map<string, NodeChange>()
|
||||
const parentMap = new Map<string, string>()
|
||||
const childrenMap = new Map<string, string[]>()
|
||||
|
||||
for (const nc of nodeChanges) {
|
||||
if (!nc.guid) continue
|
||||
|
|
@ -382,21 +383,27 @@ export function importNodeChanges(
|
|||
changeMap.set(id, nc)
|
||||
|
||||
if (nc.parentIndex?.guid) {
|
||||
parentMap.set(id, guidToString(nc.parentIndex.guid))
|
||||
const pid = guidToString(nc.parentIndex.guid)
|
||||
parentMap.set(id, pid)
|
||||
let siblings = childrenMap.get(pid)
|
||||
if (!siblings) {
|
||||
siblings = []
|
||||
childrenMap.set(pid, siblings)
|
||||
}
|
||||
siblings.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
function getChildren(ncId: string): string[] {
|
||||
const children: string[] = []
|
||||
for (const [childId, pid] of parentMap) {
|
||||
if (pid === ncId) children.push(childId)
|
||||
}
|
||||
for (const [, children] of childrenMap) {
|
||||
children.sort((a, b) => {
|
||||
const aPos = changeMap.get(a)?.parentIndex?.position ?? ''
|
||||
const bPos = changeMap.get(b)?.parentIndex?.position ?? ''
|
||||
return aPos.localeCompare(bPos)
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
function getChildren(ncId: string): string[] {
|
||||
return childrenMap.get(ncId) ?? []
|
||||
}
|
||||
|
||||
const created = new Set<string>()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
let int32 = new Int32Array(1)
|
||||
let float32 = new Float32Array(int32.buffer)
|
||||
const textDecoder = new TextDecoder()
|
||||
|
||||
export class ByteBuffer {
|
||||
private _data: Uint8Array
|
||||
|
|
@ -20,64 +21,46 @@ export class ByteBuffer {
|
|||
}
|
||||
|
||||
readByte(): number {
|
||||
if (this._index + 1 > this._data.length) {
|
||||
throw new Error('Index out of bounds')
|
||||
}
|
||||
return this._data[this._index++]
|
||||
}
|
||||
|
||||
readByteArray(): Uint8Array {
|
||||
let length = this.readVarUint()
|
||||
let start = this._index
|
||||
let end = start + length
|
||||
if (end > this._data.length) {
|
||||
throw new Error('Read array out of bounds')
|
||||
}
|
||||
this._index = end
|
||||
// Copy into a new array instead of just creating another view.
|
||||
let result = new Uint8Array(length)
|
||||
result.set(this._data.subarray(start, end))
|
||||
return result
|
||||
const length = this.readVarUint()
|
||||
const start = this._index
|
||||
this._index = start + length
|
||||
return this._data.slice(start, start + length)
|
||||
}
|
||||
|
||||
readVarFloat(): number {
|
||||
let index = this._index
|
||||
let data = this._data
|
||||
let length = data.length
|
||||
|
||||
// Optimization: use a single byte to store zero
|
||||
if (index + 1 > length) {
|
||||
throw new Error('Index out of bounds')
|
||||
}
|
||||
let first = data[index]
|
||||
const index = this._index
|
||||
const data = this._data
|
||||
const first = data[index]
|
||||
if (first === 0) {
|
||||
this._index = index + 1
|
||||
return 0
|
||||
}
|
||||
|
||||
// Endian-independent 32-bit read
|
||||
if (index + 4 > length) {
|
||||
throw new Error('Index out of bounds')
|
||||
}
|
||||
let bits = first | (data[index + 1] << 8) | (data[index + 2] << 16) | (data[index + 3] << 24)
|
||||
this._index = index + 4
|
||||
|
||||
// Move the exponent back into place
|
||||
bits = (bits << 23) | (bits >>> 9)
|
||||
|
||||
// Reinterpret as a floating-point number
|
||||
int32[0] = bits
|
||||
return float32[0]
|
||||
}
|
||||
|
||||
readVarUint(): number {
|
||||
let value = 0
|
||||
let shift = 0
|
||||
do {
|
||||
var byte = this.readByte()
|
||||
value |= (byte & 127) << shift
|
||||
shift += 7
|
||||
} while (byte & 128 && shift < 35)
|
||||
const data = this._data
|
||||
let i = this._index
|
||||
let b = data[i++]
|
||||
let value = b & 127
|
||||
if (b < 128) { this._index = i; return value }
|
||||
b = data[i++]; value |= (b & 127) << 7
|
||||
if (b < 128) { this._index = i; return value }
|
||||
b = data[i++]; value |= (b & 127) << 14
|
||||
if (b < 128) { this._index = i; return value }
|
||||
b = data[i++]; value |= (b & 127) << 21
|
||||
if (b < 128) { this._index = i; return value }
|
||||
b = data[i++]; value |= (b & 127) << 28
|
||||
this._index = i
|
||||
return value >>> 0
|
||||
}
|
||||
|
||||
|
|
@ -108,48 +91,12 @@ export class ByteBuffer {
|
|||
}
|
||||
|
||||
readString(): string {
|
||||
let result = ''
|
||||
|
||||
while (true) {
|
||||
let codePoint
|
||||
|
||||
// Decode UTF-8
|
||||
let a = this.readByte()
|
||||
if (a < 0xc0) {
|
||||
codePoint = a
|
||||
} else {
|
||||
let b = this.readByte()
|
||||
if (a < 0xe0) {
|
||||
codePoint = ((a & 0x1f) << 6) | (b & 0x3f)
|
||||
} else {
|
||||
let c = this.readByte()
|
||||
if (a < 0xf0) {
|
||||
codePoint = ((a & 0x0f) << 12) | ((b & 0x3f) << 6) | (c & 0x3f)
|
||||
} else {
|
||||
let d = this.readByte()
|
||||
codePoint = ((a & 0x07) << 18) | ((b & 0x3f) << 12) | ((c & 0x3f) << 6) | (d & 0x3f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strings are null-terminated
|
||||
if (codePoint === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
// Encode UTF-16
|
||||
if (codePoint < 0x10000) {
|
||||
result += String.fromCharCode(codePoint)
|
||||
} else {
|
||||
codePoint -= 0x10000
|
||||
result += String.fromCharCode(
|
||||
(codePoint >> 10) + 0xd800,
|
||||
(codePoint & ((1 << 10) - 1)) + 0xdc00
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
const data = this._data
|
||||
const start = this._index
|
||||
let i = start
|
||||
while (data[i] !== 0) i++
|
||||
this._index = i + 1
|
||||
return textDecoder.decode(data.subarray(start, i))
|
||||
}
|
||||
|
||||
private _growBy(amount: number): void {
|
||||
|
|
|
|||
3
tests/fixtures/material3.fig
vendored
Normal file
3
tests/fixtures/material3.fig
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:75c99e40a4a0e62db6e8f1127ef586369cc6737bd8c9f6369191ce7431f43519
|
||||
size 57312586
|
||||
3
tests/fixtures/nuxtui.fig
vendored
Normal file
3
tests/fixtures/nuxtui.fig
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:add13fd8037ea2d1c3df1e86a224425b0c11dfa093f102a60b9e122ac8ea4361
|
||||
size 85671218
|
||||
Loading…
Reference in a new issue