mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 07:14:55 +08:00
[haxe] Plan for serializer generator
This commit is contained in:
parent
3e67c82a22
commit
108f9bf355
84
tests/package-lock.json
generated
84
tests/package-lock.json
generated
@ -20,6 +20,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.1.2.tgz",
|
||||
"integrity": "sha512-yq8ZZuKuBVDgAS76LWCfFKHSYIAgqkxVB3mGVVpOe2vSkUTs7xG46zXZeNPRNVjiJuw0SZ3+J2rXiYx0RUpfGg==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
"biome": "bin/biome"
|
||||
},
|
||||
@ -49,6 +50,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@ -65,6 +67,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@ -81,6 +84,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -97,6 +101,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -113,6 +118,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -129,6 +135,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -145,6 +152,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -161,6 +169,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -177,6 +186,7 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
@ -193,6 +203,7 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@ -209,6 +220,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@ -225,6 +237,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@ -241,6 +254,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@ -257,6 +271,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@ -273,6 +288,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@ -289,6 +305,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@ -305,6 +322,7 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -321,6 +339,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -337,6 +356,7 @@
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -353,6 +373,7 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -369,6 +390,7 @@
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -385,6 +407,7 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -401,6 +424,7 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -417,6 +441,7 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -433,6 +458,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@ -449,6 +475,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
@ -465,6 +492,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
@ -481,6 +509,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
@ -497,6 +526,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
@ -513,6 +543,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
@ -529,6 +560,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
@ -545,6 +577,7 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -561,6 +594,7 @@
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -577,6 +611,7 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -606,6 +641,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz",
|
||||
"integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@ -614,6 +650,7 @@
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
@ -625,6 +662,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@ -633,6 +671,7 @@
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
@ -641,13 +680,15 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.4.0.tgz",
|
||||
"integrity": "sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/editorconfig": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
|
||||
"integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^2.19.0",
|
||||
"lru-cache": "^4.1.5",
|
||||
@ -662,7 +703,8 @@
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.8",
|
||||
@ -670,6 +712,7 @@
|
||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
@ -709,6 +752,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
@ -720,6 +764,7 @@
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
@ -733,6 +778,7 @@
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@ -746,6 +792,7 @@
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",
|
||||
"integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
@ -758,6 +805,7 @@
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
|
||||
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pseudomap": "^1.0.2",
|
||||
"yallist": "^2.1.2"
|
||||
@ -767,12 +815,14 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -781,6 +831,7 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
@ -793,6 +844,7 @@
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
@ -804,6 +856,7 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
@ -815,6 +868,7 @@
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz",
|
||||
"integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
},
|
||||
@ -827,13 +881,15 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
@ -843,6 +899,7 @@
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
@ -851,12 +908,14 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
||||
"integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
@ -874,6 +933,7 @@
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz",
|
||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@ -893,6 +953,7 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@ -907,6 +968,7 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-7.2.2.tgz",
|
||||
"integrity": "sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commandpost": "^1.0.0",
|
||||
"editorconfig": "^0.15.0"
|
||||
@ -925,12 +987,14 @@
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-jsonrpc": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz",
|
||||
"integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@ -939,6 +1003,7 @@
|
||||
"version": "3.17.5",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
|
||||
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-jsonrpc": "8.2.0",
|
||||
"vscode-languageserver-types": "3.17.5"
|
||||
@ -948,6 +1013,7 @@
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@ -955,12 +1021,14 @@
|
||||
"node_modules/vscode-languageserver-types": {
|
||||
"version": "3.17.5",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
|
||||
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="
|
||||
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
927
tests/plan-haxe.md
Normal file
927
tests/plan-haxe.md
Normal file
@ -0,0 +1,927 @@
|
||||
# Haxe Serializer Generator Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the complete implementation plan for adding Haxe support to the Spine runtime testing infrastructure. The goal is to generate a Haxe serializer that produces identical JSON output to the existing Java and C++ serializers, enabling cross-runtime compatibility testing.
|
||||
|
||||
## Current System Architecture
|
||||
|
||||
The existing system consists of three layers:
|
||||
|
||||
1. **SerializerIR Generation** (`tests/src/generate-serializer-ir.ts`)
|
||||
- Analyzes Java API to create intermediate representation
|
||||
- Outputs `tests/output/serializer-ir.json` with type and property metadata
|
||||
|
||||
2. **Language-Specific Generators**
|
||||
- `tests/src/generate-java-serializer.ts` - Java implementation
|
||||
- `tests/src/generate-cpp-serializer.ts` - C++ implementation
|
||||
- **Missing**: `tests/src/generate-haxe-serializer.ts`
|
||||
|
||||
3. **HeadlessTest Applications**
|
||||
- `spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/HeadlessTest.java`
|
||||
- `spine-cpp/tests/HeadlessTest.cpp`
|
||||
- **Missing**: `spine-haxe/tests/HeadlessTest.hx`
|
||||
|
||||
4. **Test Runner** (`tests/src/headless-test-runner.ts`)
|
||||
- Orchestrates building and running tests
|
||||
- Compares outputs for consistency
|
||||
- Currently supports: Java, C++
|
||||
- **Needs**: Haxe support
|
||||
|
||||
## SerializerIR Structure Reference
|
||||
|
||||
Based on `tests/src/generate-serializer-ir.ts:10-80`:
|
||||
|
||||
```typescript
|
||||
interface SerializerIR {
|
||||
publicMethods: PublicMethod[]; // Entry point methods
|
||||
writeMethods: WriteMethod[]; // Type-specific serializers
|
||||
enumMappings: { [enumName: string]: { [javaValue: string]: string } };
|
||||
}
|
||||
|
||||
interface WriteMethod {
|
||||
name: string; // writeSkeletonData, writeBone, etc.
|
||||
paramType: string; // Full Java class name
|
||||
properties: Property[]; // Fields to serialize
|
||||
isAbstractType: boolean; // Needs instanceof chain
|
||||
subtypeChecks?: SubtypeCheck[]; // For abstract types
|
||||
}
|
||||
|
||||
type Property = Primitive | Object | Enum | Array | NestedArray;
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Generate Haxe Serializer (`tests/src/generate-haxe-serializer.ts`)
|
||||
|
||||
Create the generator following the pattern from existing generators:
|
||||
|
||||
```typescript
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import type { Property, SerializerIR } from './generate-serializer-ir';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function transformType(javaType: string): string {
|
||||
// Java → Haxe type mappings
|
||||
const primitiveMap: Record<string, string> = {
|
||||
'String': 'String',
|
||||
'int': 'Int',
|
||||
'float': 'Float',
|
||||
'boolean': 'Bool',
|
||||
'short': 'Int',
|
||||
'byte': 'Int',
|
||||
'double': 'Float',
|
||||
'long': 'Int'
|
||||
};
|
||||
|
||||
// Remove package prefixes and map primitives
|
||||
const simpleName = javaType.includes('.') ? javaType.split('.').pop()! : javaType;
|
||||
|
||||
if (primitiveMap[simpleName]) {
|
||||
return primitiveMap[simpleName];
|
||||
}
|
||||
|
||||
// Handle arrays: Java T[] → Haxe Array<T>
|
||||
if (simpleName.endsWith('[]')) {
|
||||
const baseType = simpleName.slice(0, -2);
|
||||
return `Array<${transformType(baseType)}>`;
|
||||
}
|
||||
|
||||
// Java Array<T> stays Array<T> in Haxe
|
||||
if (simpleName.startsWith('Array<')) {
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
// Object types: keep class name, remove package
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
function mapJavaGetterToHaxeField(javaGetter: string, objName: string): string {
|
||||
// Map Java getter methods to Haxe field access
|
||||
// Based on analysis of existing Haxe classes in spine-haxe/spine-haxe/spine/
|
||||
|
||||
if (javaGetter.endsWith('()')) {
|
||||
const methodName = javaGetter.slice(0, -2);
|
||||
|
||||
// Remove get/is prefix and convert to camelCase field
|
||||
if (methodName.startsWith('get')) {
|
||||
const fieldName = methodName.slice(3);
|
||||
const haxeField = fieldName.charAt(0).toLowerCase() + fieldName.slice(1);
|
||||
return `${objName}.${haxeField}`;
|
||||
}
|
||||
|
||||
if (methodName.startsWith('is')) {
|
||||
const fieldName = methodName.slice(2);
|
||||
const haxeField = fieldName.charAt(0).toLowerCase() + fieldName.slice(1);
|
||||
return `${objName}.${haxeField}`;
|
||||
}
|
||||
|
||||
// Some methods might be direct field names
|
||||
return `${objName}.${methodName}`;
|
||||
}
|
||||
|
||||
// Direct field access (already in correct format)
|
||||
return `${objName}.${javaGetter}`;
|
||||
}
|
||||
|
||||
function generatePropertyCode(property: Property, indent: string, enumMappings: { [enumName: string]: { [javaValue: string]: string } }): string[] {
|
||||
const lines: string[] = [];
|
||||
const accessor = mapJavaGetterToHaxeField(property.getter, 'obj');
|
||||
|
||||
switch (property.kind) {
|
||||
case "primitive":
|
||||
lines.push(`${indent}json.writeValue(${accessor});`);
|
||||
break;
|
||||
|
||||
case "object":
|
||||
if (property.isNullable) {
|
||||
lines.push(`${indent}if (${accessor} == null) {`);
|
||||
lines.push(`${indent} json.writeNull();`);
|
||||
lines.push(`${indent}} else {`);
|
||||
lines.push(`${indent} ${property.writeMethodCall}(${accessor});`);
|
||||
lines.push(`${indent}}`);
|
||||
} else {
|
||||
lines.push(`${indent}${property.writeMethodCall}(${accessor});`);
|
||||
}
|
||||
break;
|
||||
|
||||
case "enum": {
|
||||
const enumName = property.enumName;
|
||||
const enumMap = enumMappings[enumName];
|
||||
|
||||
if (property.isNullable) {
|
||||
lines.push(`${indent}if (${accessor} == null) {`);
|
||||
lines.push(`${indent} json.writeNull();`);
|
||||
lines.push(`${indent}} else {`);
|
||||
}
|
||||
|
||||
if (enumMap && Object.keys(enumMap).length > 0) {
|
||||
// Generate switch statement for enum mapping
|
||||
lines.push(`${indent}${property.isNullable ? ' ' : ''}switch (${accessor}) {`);
|
||||
|
||||
for (const [javaValue, haxeValue] of Object.entries(enumMap)) {
|
||||
lines.push(`${indent}${property.isNullable ? ' ' : ''} case ${haxeValue}: json.writeValue("${javaValue}");`);
|
||||
}
|
||||
|
||||
lines.push(`${indent}${property.isNullable ? ' ' : ''} default: json.writeValue("unknown");`);
|
||||
lines.push(`${indent}${property.isNullable ? ' ' : ''}}`);
|
||||
} else {
|
||||
// Fallback using Type.enumConstructor or similar
|
||||
lines.push(`${indent}${property.isNullable ? ' ' : ''}json.writeValue(Type.enumConstructor(${accessor}));`);
|
||||
}
|
||||
|
||||
if (property.isNullable) {
|
||||
lines.push(`${indent}}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "array": {
|
||||
if (property.isNullable) {
|
||||
lines.push(`${indent}if (${accessor} == null) {`);
|
||||
lines.push(`${indent} json.writeNull();`);
|
||||
lines.push(`${indent}} else {`);
|
||||
lines.push(`${indent} json.writeArrayStart();`);
|
||||
lines.push(`${indent} for (item in ${accessor}) {`);
|
||||
} else {
|
||||
lines.push(`${indent}json.writeArrayStart();`);
|
||||
lines.push(`${indent}for (item in ${accessor}) {`);
|
||||
}
|
||||
|
||||
const itemIndent = property.isNullable ? `${indent} ` : `${indent} `;
|
||||
if (property.elementKind === "primitive") {
|
||||
lines.push(`${itemIndent}json.writeValue(item);`);
|
||||
} else {
|
||||
lines.push(`${itemIndent}${property.writeMethodCall}(item);`);
|
||||
}
|
||||
|
||||
if (property.isNullable) {
|
||||
lines.push(`${indent} }`);
|
||||
lines.push(`${indent} json.writeArrayEnd();`);
|
||||
lines.push(`${indent}}`);
|
||||
} else {
|
||||
lines.push(`${indent}}`);
|
||||
lines.push(`${indent}json.writeArrayEnd();`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "nestedArray": {
|
||||
if (property.isNullable) {
|
||||
lines.push(`${indent}if (${accessor} == null) {`);
|
||||
lines.push(`${indent} json.writeNull();`);
|
||||
lines.push(`${indent}} else {`);
|
||||
}
|
||||
|
||||
const outerIndent = property.isNullable ? `${indent} ` : indent;
|
||||
lines.push(`${outerIndent}json.writeArrayStart();`);
|
||||
lines.push(`${outerIndent}for (nestedArray in ${accessor}) {`);
|
||||
lines.push(`${outerIndent} if (nestedArray == null) {`);
|
||||
lines.push(`${outerIndent} json.writeNull();`);
|
||||
lines.push(`${outerIndent} } else {`);
|
||||
lines.push(`${outerIndent} json.writeArrayStart();`);
|
||||
lines.push(`${outerIndent} for (elem in nestedArray) {`);
|
||||
lines.push(`${outerIndent} json.writeValue(elem);`);
|
||||
lines.push(`${outerIndent} }`);
|
||||
lines.push(`${outerIndent} json.writeArrayEnd();`);
|
||||
lines.push(`${outerIndent} }`);
|
||||
lines.push(`${outerIndent}}`);
|
||||
lines.push(`${outerIndent}json.writeArrayEnd();`);
|
||||
|
||||
if (property.isNullable) {
|
||||
lines.push(`${indent}}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
function generateHaxeFromIR(ir: SerializerIR): string {
|
||||
const haxeOutput: string[] = [];
|
||||
|
||||
// Generate Haxe file header
|
||||
haxeOutput.push('package spine.utils;');
|
||||
haxeOutput.push('');
|
||||
haxeOutput.push('import haxe.ds.StringMap;');
|
||||
haxeOutput.push('import spine.*;');
|
||||
haxeOutput.push('import spine.animation.*;');
|
||||
haxeOutput.push('import spine.attachments.*;');
|
||||
haxeOutput.push('');
|
||||
haxeOutput.push('class SkeletonSerializer {');
|
||||
haxeOutput.push(' private var visitedObjects:StringMap<String> = new StringMap();');
|
||||
haxeOutput.push(' private var nextId:Int = 1;');
|
||||
haxeOutput.push(' private var json:JsonWriter;');
|
||||
haxeOutput.push('');
|
||||
haxeOutput.push(' public function new() {}');
|
||||
haxeOutput.push('');
|
||||
|
||||
// Generate public methods
|
||||
for (const method of ir.publicMethods) {
|
||||
const haxeParamType = transformType(method.paramType);
|
||||
haxeOutput.push(` public function ${method.name}(${method.paramName}:${haxeParamType}):String {`);
|
||||
haxeOutput.push(' visitedObjects = new StringMap();');
|
||||
haxeOutput.push(' nextId = 1;');
|
||||
haxeOutput.push(' json = new JsonWriter();');
|
||||
haxeOutput.push(` ${method.writeMethodCall}(${method.paramName});`);
|
||||
haxeOutput.push(' return json.getString();');
|
||||
haxeOutput.push(' }');
|
||||
haxeOutput.push('');
|
||||
}
|
||||
|
||||
// Generate write methods
|
||||
for (const method of ir.writeMethods) {
|
||||
const shortName = method.paramType.split('.').pop();
|
||||
const haxeType = transformType(method.paramType);
|
||||
|
||||
haxeOutput.push(` private function ${method.name}(obj:${haxeType}):Void {`);
|
||||
|
||||
if (method.isAbstractType) {
|
||||
// Handle abstract types with Std.isOfType chain (Haxe equivalent of instanceof)
|
||||
if (method.subtypeChecks && method.subtypeChecks.length > 0) {
|
||||
let first = true;
|
||||
for (const subtype of method.subtypeChecks) {
|
||||
const subtypeHaxeName = transformType(subtype.typeName);
|
||||
|
||||
if (first) {
|
||||
haxeOutput.push(` if (Std.isOfType(obj, ${subtypeHaxeName})) {`);
|
||||
first = false;
|
||||
} else {
|
||||
haxeOutput.push(` } else if (Std.isOfType(obj, ${subtypeHaxeName})) {`);
|
||||
}
|
||||
haxeOutput.push(` ${subtype.writeMethodCall}(cast(obj, ${subtypeHaxeName}));`);
|
||||
}
|
||||
haxeOutput.push(' } else {');
|
||||
haxeOutput.push(` throw new spine.SpineException("Unknown ${shortName} type");`);
|
||||
haxeOutput.push(' }');
|
||||
} else {
|
||||
haxeOutput.push(' json.writeNull(); // No concrete implementations after filtering exclusions');
|
||||
}
|
||||
} else {
|
||||
// Handle concrete types - add cycle detection
|
||||
haxeOutput.push(' if (visitedObjects.exists(obj)) {');
|
||||
haxeOutput.push(' json.writeValue(visitedObjects.get(obj));');
|
||||
haxeOutput.push(' return;');
|
||||
haxeOutput.push(' }');
|
||||
|
||||
// Generate reference string
|
||||
const nameGetter = method.properties.find(p =>
|
||||
(p.kind === 'object' || p.kind === "primitive") &&
|
||||
p.getter === 'getName()' &&
|
||||
p.valueType === 'String'
|
||||
);
|
||||
|
||||
if (nameGetter) {
|
||||
const nameAccessor = mapJavaGetterToHaxeField('getName()', 'obj');
|
||||
haxeOutput.push(` var refString = ${nameAccessor} != null ? "<${shortName}-" + ${nameAccessor} + ">" : "<${shortName}-" + (nextId++) + ">";`);
|
||||
} else {
|
||||
haxeOutput.push(` var refString = "<${shortName}-" + (nextId++) + ">";`);
|
||||
}
|
||||
haxeOutput.push(' visitedObjects.set(obj, refString);');
|
||||
haxeOutput.push('');
|
||||
|
||||
haxeOutput.push(' json.writeObjectStart();');
|
||||
|
||||
// Write reference string and type
|
||||
haxeOutput.push(' json.writeName("refString");');
|
||||
haxeOutput.push(' json.writeValue(refString);');
|
||||
haxeOutput.push(' json.writeName("type");');
|
||||
haxeOutput.push(` json.writeValue("${shortName}");`);
|
||||
|
||||
// Write properties
|
||||
for (const property of method.properties) {
|
||||
haxeOutput.push('');
|
||||
haxeOutput.push(` json.writeName("${property.name}");`);
|
||||
const propertyLines = generatePropertyCode(property, ' ', ir.enumMappings);
|
||||
haxeOutput.push(...propertyLines);
|
||||
}
|
||||
|
||||
haxeOutput.push('');
|
||||
haxeOutput.push(' json.writeObjectEnd();');
|
||||
}
|
||||
|
||||
haxeOutput.push(' }');
|
||||
haxeOutput.push('');
|
||||
}
|
||||
|
||||
// Add helper methods for special types (following C++ pattern)
|
||||
haxeOutput.push(' // Helper methods for special types');
|
||||
haxeOutput.push(' private function writeColor(obj:spine.Color):Void {');
|
||||
haxeOutput.push(' if (obj == null) {');
|
||||
haxeOutput.push(' json.writeNull();');
|
||||
haxeOutput.push(' } else {');
|
||||
haxeOutput.push(' json.writeObjectStart();');
|
||||
haxeOutput.push(' json.writeName("r");');
|
||||
haxeOutput.push(' json.writeValue(obj.r);');
|
||||
haxeOutput.push(' json.writeName("g");');
|
||||
haxeOutput.push(' json.writeValue(obj.g);');
|
||||
haxeOutput.push(' json.writeName("b");');
|
||||
haxeOutput.push(' json.writeValue(obj.b);');
|
||||
haxeOutput.push(' json.writeName("a");');
|
||||
haxeOutput.push(' json.writeValue(obj.a);');
|
||||
haxeOutput.push(' json.writeObjectEnd();');
|
||||
haxeOutput.push(' }');
|
||||
haxeOutput.push(' }');
|
||||
haxeOutput.push('');
|
||||
|
||||
haxeOutput.push('}');
|
||||
|
||||
return haxeOutput.join('\n');
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
// Read the IR file
|
||||
const irFile = path.resolve(__dirname, '../output/serializer-ir.json');
|
||||
if (!fs.existsSync(irFile)) {
|
||||
console.error('Serializer IR not found. Run generate-serializer-ir.ts first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8'));
|
||||
|
||||
// Generate Haxe serializer from IR
|
||||
const haxeCode = generateHaxeFromIR(ir);
|
||||
|
||||
// Write the Haxe file
|
||||
const haxeFile = path.resolve(
|
||||
__dirname,
|
||||
'../../spine-haxe/spine-haxe/spine/utils/SkeletonSerializer.hx'
|
||||
);
|
||||
|
||||
fs.mkdirSync(path.dirname(haxeFile), { recursive: true });
|
||||
fs.writeFileSync(haxeFile, haxeCode);
|
||||
|
||||
console.log(`Generated Haxe serializer from IR: ${haxeFile}`);
|
||||
console.log(`- ${ir.publicMethods.length} public methods`);
|
||||
console.log(`- ${ir.writeMethods.length} write methods`);
|
||||
console.log(`- ${Object.keys(ir.enumMappings).length} enum mappings`);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error:', error.message);
|
||||
console.error('Stack:', error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow running as a script or importing the function
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main();
|
||||
}
|
||||
|
||||
export { generateHaxeFromIR };
|
||||
```
|
||||
|
||||
### 2. JsonWriter Helper Class (`spine-haxe/spine-haxe/spine/utils/JsonWriter.hx`)
|
||||
|
||||
Based on the pattern from `spine-cpp/tests/JsonWriter.h`, create a Haxe equivalent:
|
||||
|
||||
```haxe
|
||||
package spine.utils;
|
||||
|
||||
enum JsonContext {
|
||||
Object;
|
||||
Array;
|
||||
}
|
||||
|
||||
class JsonWriter {
|
||||
private var buffer:StringBuf = new StringBuf();
|
||||
private var needsComma:Bool = false;
|
||||
private var contexts:Array<JsonContext> = [];
|
||||
|
||||
public function new() {
|
||||
buffer = new StringBuf();
|
||||
needsComma = false;
|
||||
contexts = [];
|
||||
}
|
||||
|
||||
public function writeObjectStart():Void {
|
||||
writeCommaIfNeeded();
|
||||
buffer.add("{");
|
||||
contexts.push(Object);
|
||||
needsComma = false;
|
||||
}
|
||||
|
||||
public function writeObjectEnd():Void {
|
||||
buffer.add("}");
|
||||
contexts.pop();
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
public function writeArrayStart():Void {
|
||||
writeCommaIfNeeded();
|
||||
buffer.add("[");
|
||||
contexts.push(Array);
|
||||
needsComma = false;
|
||||
}
|
||||
|
||||
public function writeArrayEnd():Void {
|
||||
buffer.add("]");
|
||||
contexts.pop();
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
public function writeName(name:String):Void {
|
||||
writeCommaIfNeeded();
|
||||
buffer.add('"${escapeString(name)}":');
|
||||
needsComma = false;
|
||||
}
|
||||
|
||||
public function writeValue(value:Dynamic):Void {
|
||||
writeCommaIfNeeded();
|
||||
|
||||
if (value == null) {
|
||||
buffer.add("null");
|
||||
} else if (Std.isOfType(value, String)) {
|
||||
buffer.add('"${escapeString(cast(value, String))}"');
|
||||
} else if (Std.isOfType(value, Bool)) {
|
||||
buffer.add(value ? "true" : "false");
|
||||
} else if (Std.isOfType(value, Float) || Std.isOfType(value, Int)) {
|
||||
// Ensure consistent float formatting (C locale style)
|
||||
buffer.add(Std.string(value));
|
||||
} else {
|
||||
buffer.add(Std.string(value));
|
||||
}
|
||||
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
public function writeNull():Void {
|
||||
writeCommaIfNeeded();
|
||||
buffer.add("null");
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
public function getString():String {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private function writeCommaIfNeeded():Void {
|
||||
if (needsComma) {
|
||||
buffer.add(",");
|
||||
}
|
||||
}
|
||||
|
||||
private function escapeString(str:String):String {
|
||||
// Escape special characters for JSON
|
||||
str = StringTools.replace(str, "\\", "\\\\");
|
||||
str = StringTools.replace(str, '"', '\\"');
|
||||
str = StringTools.replace(str, "\n", "\\n");
|
||||
str = StringTools.replace(str, "\r", "\\r");
|
||||
str = StringTools.replace(str, "\t", "\\t");
|
||||
return str;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Haxe HeadlessTest Application (`spine-haxe/tests/HeadlessTest.hx`)
|
||||
|
||||
Following the pattern from existing HeadlessTest implementations:
|
||||
|
||||
```haxe
|
||||
package;
|
||||
|
||||
import spine.*;
|
||||
import spine.atlas.TextureAtlas;
|
||||
import spine.atlas.TextureAtlasPage;
|
||||
import spine.atlas.TextureLoader;
|
||||
import spine.attachments.AtlasAttachmentLoader;
|
||||
import spine.animation.*;
|
||||
import spine.utils.SkeletonSerializer;
|
||||
import sys.io.File;
|
||||
import haxe.io.Bytes;
|
||||
|
||||
// Mock texture loader that doesn't require actual texture loading
|
||||
class MockTextureLoader implements TextureLoader {
|
||||
public function new() {}
|
||||
|
||||
public function load(page:TextureAtlasPage, path:String):Void {
|
||||
// Set mock dimensions - no actual texture loading needed
|
||||
page.width = 1024;
|
||||
page.height = 1024;
|
||||
page.texture = {}; // Empty object as mock texture
|
||||
}
|
||||
|
||||
public function unload(texture:Dynamic):Void {
|
||||
// Nothing to unload in headless mode
|
||||
}
|
||||
}
|
||||
|
||||
class HeadlessTest {
|
||||
static function main():Void {
|
||||
var args = Sys.args();
|
||||
|
||||
if (args.length < 2) {
|
||||
Sys.stderr().writeString("Usage: HeadlessTest <skeleton-path> <atlas-path> [animation-name]\n");
|
||||
Sys.exit(1);
|
||||
}
|
||||
|
||||
var skeletonPath = args[0];
|
||||
var atlasPath = args[1];
|
||||
var animationName = args.length >= 3 ? args[2] : null;
|
||||
|
||||
try {
|
||||
// Load atlas with mock texture loader
|
||||
var textureLoader = new MockTextureLoader();
|
||||
var atlasContent = File.getContent(atlasPath);
|
||||
var atlas = new TextureAtlas(atlasContent, textureLoader);
|
||||
|
||||
// Load skeleton data
|
||||
var skeletonData:SkeletonData;
|
||||
var attachmentLoader = new AtlasAttachmentLoader(atlas);
|
||||
|
||||
if (StringTools.endsWith(skeletonPath, ".json")) {
|
||||
var loader = new SkeletonJson(attachmentLoader);
|
||||
var jsonContent = File.getContent(skeletonPath);
|
||||
skeletonData = loader.readSkeletonData(jsonContent);
|
||||
} else {
|
||||
var loader = new SkeletonBinary(attachmentLoader);
|
||||
var binaryContent = File.getBytes(skeletonPath);
|
||||
skeletonData = loader.readSkeletonData(binaryContent);
|
||||
}
|
||||
|
||||
// Create serializer
|
||||
var serializer = new SkeletonSerializer();
|
||||
|
||||
// Print skeleton data
|
||||
Sys.println("=== SKELETON DATA ===");
|
||||
Sys.println(serializer.serializeSkeletonData(skeletonData));
|
||||
|
||||
// Create skeleton instance
|
||||
var skeleton = new Skeleton(skeletonData);
|
||||
|
||||
// Handle animation if provided
|
||||
var state:AnimationState = null;
|
||||
if (animationName != null) {
|
||||
var stateData = new AnimationStateData(skeletonData);
|
||||
state = new AnimationState(stateData);
|
||||
|
||||
var animation = skeletonData.findAnimation(animationName);
|
||||
if (animation == null) {
|
||||
Sys.stderr().writeString('Animation not found: $animationName\n');
|
||||
Sys.exit(1);
|
||||
}
|
||||
|
||||
state.setAnimation(0, animation, true);
|
||||
state.update(0.016);
|
||||
state.apply(skeleton);
|
||||
}
|
||||
|
||||
// Update world transforms (following the pattern from other HeadlessTests)
|
||||
skeleton.updateWorldTransform(Physics.update);
|
||||
|
||||
// Print skeleton state
|
||||
Sys.println("\n=== SKELETON STATE ===");
|
||||
Sys.println(serializer.serializeSkeleton(skeleton));
|
||||
|
||||
// Print animation state if present
|
||||
if (state != null) {
|
||||
Sys.println("\n=== ANIMATION STATE ===");
|
||||
Sys.println(serializer.serializeAnimationState(state));
|
||||
}
|
||||
|
||||
} catch (e:Dynamic) {
|
||||
Sys.stderr().writeString('Error: $e\n');
|
||||
Sys.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Build Script (`spine-haxe/build-headless-test.sh`)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Build Haxe HeadlessTest for cross-platform execution
|
||||
# Following pattern from spine-cpp/build.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo "Building Haxe HeadlessTest..."
|
||||
|
||||
# Clean previous build
|
||||
rm -rf build/headless-test
|
||||
|
||||
# Create build directory
|
||||
mkdir -p build
|
||||
|
||||
# Compile HeadlessTest to C++ for performance and consistency with other runtimes
|
||||
haxe \
|
||||
-cp spine-haxe \
|
||||
-cp tests \
|
||||
-main HeadlessTest \
|
||||
-cpp build/headless-test \
|
||||
-D HXCPP_QUIET
|
||||
|
||||
# Make executable
|
||||
chmod +x build/headless-test/HeadlessTest
|
||||
|
||||
echo "Build complete: build/headless-test/HeadlessTest"
|
||||
```
|
||||
|
||||
### 5. Test Runner Integration (`tests/src/headless-test-runner.ts`)
|
||||
|
||||
Add Haxe support to the existing test runner. Key changes needed:
|
||||
|
||||
```typescript
|
||||
// Line 207: Update supported languages
|
||||
if (!['cpp', 'haxe'].includes(language)) {
|
||||
log_detail(`Invalid target language: ${language}. Must be cpp or haxe`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Add needsHaxeBuild function (similar to needsCppBuild at line 96)
|
||||
function needsHaxeBuild(): boolean {
|
||||
const haxeDir = join(SPINE_ROOT, 'spine-haxe');
|
||||
const buildDir = join(haxeDir, 'build');
|
||||
const headlessTest = join(buildDir, 'headless-test', 'HeadlessTest');
|
||||
|
||||
try {
|
||||
// Check if executable exists
|
||||
if (!existsSync(headlessTest)) return true;
|
||||
|
||||
// Get executable modification time
|
||||
const execTime = statSync(headlessTest).mtime.getTime();
|
||||
|
||||
// Check Haxe source files
|
||||
const haxeSourceTime = getNewestFileTime(join(haxeDir, 'spine-haxe'), '*.hx');
|
||||
const testSourceTime = getNewestFileTime(join(haxeDir, 'tests'), '*.hx');
|
||||
const buildScriptTime = getNewestFileTime(haxeDir, 'build-headless-test.sh');
|
||||
|
||||
const newestSourceTime = Math.max(haxeSourceTime, testSourceTime, buildScriptTime);
|
||||
|
||||
return newestSourceTime > execTime;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add executeHaxe function (similar to executeCpp at line 321)
|
||||
function executeHaxe(args: TestArgs): string {
|
||||
const haxeDir = join(SPINE_ROOT, 'spine-haxe');
|
||||
const testsDir = join(haxeDir, 'tests');
|
||||
|
||||
if (!existsSync(testsDir)) {
|
||||
log_detail(`Haxe tests directory not found: ${testsDir}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if we need to build
|
||||
if (needsHaxeBuild()) {
|
||||
log_action('Building Haxe HeadlessTest');
|
||||
try {
|
||||
execSync('./build-headless-test.sh', {
|
||||
cwd: haxeDir,
|
||||
stdio: ['inherit', 'pipe', 'inherit']
|
||||
});
|
||||
log_ok();
|
||||
} catch (error: any) {
|
||||
log_fail();
|
||||
log_detail(`Haxe build failed: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the headless test
|
||||
const testArgs = [args.skeletonPath, args.atlasPath];
|
||||
if (args.animationName) {
|
||||
testArgs.push(args.animationName);
|
||||
}
|
||||
|
||||
const buildDir = join(haxeDir, 'build');
|
||||
const headlessTest = join(buildDir, 'headless-test', 'HeadlessTest');
|
||||
|
||||
if (!existsSync(headlessTest)) {
|
||||
log_detail(`Haxe headless-test executable not found: ${headlessTest}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log_action('Running Haxe HeadlessTest');
|
||||
try {
|
||||
const output = execSync(`${headlessTest} ${testArgs.join(' ')}`, {
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large output
|
||||
});
|
||||
log_ok();
|
||||
return output;
|
||||
} catch (error: any) {
|
||||
log_fail();
|
||||
log_detail(`Haxe execution failed: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update runTestsForFiles function around line 525 to handle Haxe
|
||||
if (language === 'cpp') {
|
||||
targetOutput = executeCpp(testArgs);
|
||||
} else if (language === 'haxe') {
|
||||
targetOutput = executeHaxe(testArgs);
|
||||
} else {
|
||||
log_detail(`Unsupported target language: ${language}`);
|
||||
process.exit(1);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Build Integration (`tests/generate-serializers.sh`)
|
||||
|
||||
Update the serializer generation script to include Haxe:
|
||||
|
||||
```bash
|
||||
# Add after C++ generation
|
||||
echo "Generating Haxe serializer..."
|
||||
tsx tests/src/generate-haxe-serializer.ts
|
||||
|
||||
echo "Type checking Haxe serializer..."
|
||||
cd spine-haxe && haxe -cp spine-haxe --no-output -main spine.utils.SkeletonSerializer
|
||||
cd ..
|
||||
```
|
||||
|
||||
### 7. File Structure Summary
|
||||
|
||||
```
|
||||
spine-haxe/
|
||||
├── spine-haxe/spine/utils/
|
||||
│ ├── SkeletonSerializer.hx (generated)
|
||||
│ └── JsonWriter.hx (helper class)
|
||||
├── tests/
|
||||
│ └── HeadlessTest.hx (console application)
|
||||
├── build-headless-test.sh (build script)
|
||||
└── build/headless-test/ (compiled executable)
|
||||
└── HeadlessTest
|
||||
|
||||
tests/src/
|
||||
├── generate-haxe-serializer.ts (new generator)
|
||||
└── headless-test-runner.ts (updated with Haxe support)
|
||||
```
|
||||
|
||||
## Type Checking and Validation
|
||||
|
||||
### Compilation Validation
|
||||
|
||||
Add type checking to the generator to ensure generated code compiles:
|
||||
|
||||
```typescript
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
async function validateGeneratedHaxeCode(haxeCode: string, outputPath: string): Promise<void> {
|
||||
// Write code to file
|
||||
fs.writeFileSync(outputPath, haxeCode);
|
||||
|
||||
try {
|
||||
// Attempt compilation without output (type check only)
|
||||
execSync('haxe -cp spine-haxe --no-output -main spine.utils.SkeletonSerializer', {
|
||||
cwd: path.resolve(__dirname, '../../spine-haxe'),
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
console.log('✓ Generated Haxe serializer compiles successfully');
|
||||
|
||||
} catch (error: any) {
|
||||
fs.unlinkSync(outputPath);
|
||||
throw new Error(`Generated Haxe serializer failed to compile:\n${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Call in main() after generating code
|
||||
await validateGeneratedHaxeCode(haxeCode, haxeFile);
|
||||
```
|
||||
|
||||
## Key Implementation Notes
|
||||
|
||||
### Java → Haxe Property Mapping
|
||||
|
||||
Based on analysis of `spine-haxe/spine-haxe/spine/` classes:
|
||||
|
||||
- `obj.getName()` → `obj.name`
|
||||
- `obj.getBones()` → `obj.bones`
|
||||
- `obj.isActive()` → `obj.active`
|
||||
- `obj.getColor()` → `obj.color`
|
||||
|
||||
### Enum Handling
|
||||
|
||||
Haxe enums are different from Java enums. Use `Type.enumConstructor()` to get string representation:
|
||||
|
||||
```haxe
|
||||
// For enum serialization
|
||||
json.writeValue(Type.enumConstructor(obj.blendMode));
|
||||
```
|
||||
|
||||
### Array Handling
|
||||
|
||||
Haxe uses `Array<T>` syntax similar to Java, but iteration is different:
|
||||
|
||||
```haxe
|
||||
// Haxe iteration
|
||||
for (item in obj.bones) {
|
||||
writeBone(item);
|
||||
}
|
||||
```
|
||||
|
||||
### Null Safety
|
||||
|
||||
Haxe has explicit null checking:
|
||||
|
||||
```haxe
|
||||
if (obj.skin == null) {
|
||||
json.writeNull();
|
||||
} else {
|
||||
writeSkin(obj.skin);
|
||||
}
|
||||
```
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
### Cross-Runtime Consistency
|
||||
|
||||
The test runner will automatically:
|
||||
|
||||
1. Build all three runtimes (Java, C++, Haxe)
|
||||
2. Run identical test cases on same skeleton files
|
||||
3. Compare JSON outputs for exact matches
|
||||
4. Report any differences
|
||||
|
||||
### Manual Testing
|
||||
|
||||
```bash
|
||||
# Generate all serializers
|
||||
./tests/generate-serializers.sh
|
||||
|
||||
# Test specific skeleton with all runtimes
|
||||
tsx tests/src/headless-test-runner.ts cpp -s spineboy idle
|
||||
tsx tests/src/headless-test-runner.ts haxe -s spineboy idle
|
||||
|
||||
# Compare outputs
|
||||
diff tests/output/skeleton-data-cpp-json.json tests/output/skeleton-data-haxe-json.json
|
||||
```
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Create `tests/src/generate-haxe-serializer.ts`
|
||||
- [ ] Create `spine-haxe/spine-haxe/spine/utils/JsonWriter.hx`
|
||||
- [ ] Create `spine-haxe/tests/HeadlessTest.hx`
|
||||
- [ ] Create `spine-haxe/build-headless-test.sh`
|
||||
- [ ] Update `tests/src/headless-test-runner.ts` with Haxe support
|
||||
- [ ] Update `tests/generate-serializers.sh`
|
||||
- [ ] Test with existing skeleton examples
|
||||
- [ ] Verify JSON output matches Java/C++ exactly
|
||||
- [ ] Add to CI pipeline
|
||||
|
||||
## Expected Benefits
|
||||
|
||||
1. **Cross-Runtime Testing**: Verify Haxe runtime behavior matches Java/C++
|
||||
2. **Debugging Support**: Unified JSON format for inspection across all runtimes
|
||||
3. **API Consistency**: Ensure Haxe API changes don't break compatibility
|
||||
4. **Quality Assurance**: Automated verification of serialization correctness
|
||||
5. **Development Velocity**: Fast detection of runtime-specific issues
|
||||
|
||||
This implementation follows the established patterns while adapting to Haxe's specific language features and build system.
|
||||
@ -1,8 +1,15 @@
|
||||
- Port C++ SkeletonRenderer and RenderCommands to all runtimes
|
||||
- Will be used to snapshottesting via HeadlessTest, see also tests/
|
||||
- Can go into main package in all core runtimes, except for spine-libgdx, where it must go next to SkeletonSerializer in spine-libgdx-tests
|
||||
- Generate language bindings in spine-c/codegen
|
||||
- Use CClassOrStruct, CEnum that get generated from spine-cpp-types.json and generate
|
||||
- Swift
|
||||
- Dart
|
||||
- Fix Dart NativeArray wrt to resize/add/remove. Current impl is wonky. Either make it read-only or support full mutabiliy (prefer latter)
|
||||
- Generate bindings for Swift from spine-c generate() like dart-writer.ts
|
||||
- Generate Godot wrappers from C++ types and/or spine-c generate() (unlike dart-writer.ts)?
|
||||
- headless-test improvements
|
||||
- should take cli args for ad-hoc testing
|
||||
- if none are given, should execute a set of (regression) tests and output individual test snapshots one after the other as jsonl
|
||||
- All headless tests must have the same test suite
|
||||
- test runner must know how to deal with this mode
|
||||
- Add serializer generator for Haxe (see tests/plan-haxe.md for a full plan)
|
||||
- Add serializer generator for C#
|
||||
- Add serializer generator for TypeScript
|
||||
- spine-c/codegen type extractor should also report typedefs like typedef long long PropertyId; so primitive type to some name, and we need to handle that in the codegen
|
||||
Loading…
x
Reference in New Issue
Block a user