@defold-typescript/transpiler 0.30.0 → 0.32.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/animation-sources.d.ts +6 -0
- package/dist/{sprite-animation-index.d.ts → component-animation-index.d.ts} +4 -4
- package/dist/config-key-index.d.ts +1 -0
- package/dist/gui-flipbook-index.d.ts +8 -0
- package/dist/gui-node-index.d.ts +21 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +1326 -396
- package/dist/library-dependencies.d.ts +47 -0
- package/dist/scene-address-declaration.d.ts +4 -2
- package/dist/scene-collection-roles.d.ts +30 -0
- package/dist/scene-component-index.d.ts +20 -0
- package/dist/scene-documents.d.ts +3 -0
- package/dist/scene-naming-context.d.ts +66 -0
- package/dist/scene-object-path-index.d.ts +4 -1
- package/dist/url-address-literals.d.ts +10 -0
- package/dist/url-address-slots.d.ts +3 -1
- package/dist/url-cross-world-address.d.ts +41 -0
- package/dist/url-fragment-reachability.d.ts +3 -0
- package/package.json +2 -2
- package/src/animation-sources.ts +90 -0
- package/src/component-animation-index.ts +399 -0
- package/src/config-key-index.ts +28 -2
- package/src/gui-flipbook-index.ts +91 -0
- package/src/gui-node-index.ts +42 -4
- package/src/index.ts +52 -10
- package/src/library-dependencies.ts +337 -0
- package/src/scene-address-declaration.ts +29 -12
- package/src/scene-collection-roles.ts +267 -0
- package/src/scene-component-index.ts +74 -6
- package/src/scene-documents.ts +66 -6
- package/src/scene-naming-context.ts +130 -0
- package/src/scene-object-path-index.ts +172 -33
- package/src/transpile.ts +3 -0
- package/src/url-address-literals.ts +115 -0
- package/src/url-address-slots.ts +23 -2
- package/src/url-cross-world-address.ts +109 -0
- package/src/url-fragment-reachability.ts +72 -93
- package/src/sprite-animation-index.ts +0 -241
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type SceneReadHost } from "./scene-documents";
|
|
2
|
+
export interface ExtensionDependency {
|
|
3
|
+
readonly index: number;
|
|
4
|
+
readonly url: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function readGameProjectDependencies(gameProjectText: string): ExtensionDependency[];
|
|
7
|
+
export declare function archiveWrapperOf(entries: readonly string[]): string | undefined;
|
|
8
|
+
export interface LibrarySharedEntry {
|
|
9
|
+
readonly entry: string;
|
|
10
|
+
readonly path: string;
|
|
11
|
+
}
|
|
12
|
+
export interface LibraryIncludedEntries {
|
|
13
|
+
readonly shared: LibrarySharedEntry[];
|
|
14
|
+
readonly reasons: string[];
|
|
15
|
+
readonly refused: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function isContainedResourcePath(path: string): boolean;
|
|
18
|
+
export declare function libraryIncludedEntries(entries: readonly string[], gameProjectText: string | undefined): LibraryIncludedEntries;
|
|
19
|
+
export declare const LIBRARY_DEPENDENCY_ROOT = ".defold-types/dependencies";
|
|
20
|
+
export interface LibrarySceneDocument {
|
|
21
|
+
readonly displayPath: string;
|
|
22
|
+
readonly hostPath: string;
|
|
23
|
+
readonly url: string;
|
|
24
|
+
readonly text: string;
|
|
25
|
+
}
|
|
26
|
+
export interface LibrarySceneDocuments {
|
|
27
|
+
readonly documents: LibrarySceneDocument[];
|
|
28
|
+
readonly unreadable: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The scene sources the project's resolved library dependencies contribute,
|
|
32
|
+
* read out of the surface `resolve` materialized.
|
|
33
|
+
*
|
|
34
|
+
* Each document is keyed by its **merged** Defold resource path, not by one
|
|
35
|
+
* namespaced with its origin: Defold folds a library's `[library] include_dirs`
|
|
36
|
+
* into the consuming project's root namespace, and `scene-object-path-index.ts`
|
|
37
|
+
* resolves a `collection: "/druid/foo.collection"` reference by exactly that
|
|
38
|
+
* key — so an origin-namespaced key would strand every cross-document
|
|
39
|
+
* reference. The origin travels beside the document instead.
|
|
40
|
+
*
|
|
41
|
+
* Every refusal is one `unreadable` line naming the URL where one is known: an
|
|
42
|
+
* absent or unusable manifest, a declared dependency the last `resolve` did not
|
|
43
|
+
* materialize, a materialized directory `game.project` no longer declares, and
|
|
44
|
+
* a file that would not read. A dependency that contributes nothing silently is
|
|
45
|
+
* exactly the hole `readSceneDocuments` promises not to have.
|
|
46
|
+
*/
|
|
47
|
+
export declare function readLibrarySceneDocuments(host: SceneReadHost, projectRoot: string, extensions?: readonly string[]): LibrarySceneDocuments;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { SceneCollectionRoles } from "./scene-collection-roles";
|
|
1
2
|
/**
|
|
2
3
|
* The global augmentation that fills `SceneGameObjectAddresses` and
|
|
3
4
|
* `SceneComponentAddresses` from already-read scene sources — keys are
|
|
4
|
-
* project-relative display paths, values are file text.
|
|
5
|
+
* project-relative display paths, values are file text. `roles` says which
|
|
6
|
+
* world each collection is, so a path key carries the world it resolves in.
|
|
5
7
|
*
|
|
6
8
|
* Pure, like the two indexes it composes: the filesystem walk belongs to the
|
|
7
9
|
* caller. Keys are sorted so a project whose scenes did not change re-emits a
|
|
@@ -9,4 +11,4 @@
|
|
|
9
11
|
* same bytes as well; a `Set` iteration order leaking into the output would
|
|
10
12
|
* make every regeneration rewrite the file and re-check the whole program.
|
|
11
13
|
*/
|
|
12
|
-
export declare function buildSceneAddressDeclaration(documents: ReadonlyMap<string, string
|
|
14
|
+
export declare function buildSceneAddressDeclaration(documents: ReadonlyMap<string, string>, roles: SceneCollectionRoles): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface SceneCollectionRoles {
|
|
2
|
+
/** The one collection `[bootstrap] main_collection` names, whose paths are bare. */
|
|
3
|
+
readonly bootstrap?: string | undefined;
|
|
4
|
+
/** Proxied collections, mapped to the socket `CollectionDesc.name` gives them. */
|
|
5
|
+
readonly sockets: ReadonlyMap<string, string>;
|
|
6
|
+
/** Collections another collection instances, whose paths exist only under that id. */
|
|
7
|
+
readonly instanced: ReadonlySet<string>;
|
|
8
|
+
/** Collection-factory prototypes, whose objects have no static path at all. */
|
|
9
|
+
readonly factoryPrototypes: ReadonlySet<string>;
|
|
10
|
+
readonly incomplete: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface SceneCollectionRolesInput {
|
|
13
|
+
/** The `.go`/`.collection` universe, keyed by display path. */
|
|
14
|
+
readonly documents: ReadonlyMap<string, string>;
|
|
15
|
+
/** The `.collectionproxy`/`.collectionfactory` documents, keyed by display path. */
|
|
16
|
+
readonly references: ReadonlyMap<string, string>;
|
|
17
|
+
/** `game.project`'s text, or `undefined` when the walk could not read it. */
|
|
18
|
+
readonly gameProject: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Classify every collection the project declares into the world it is: the one
|
|
22
|
+
* bootstrap root, a proxy world addressed through its own socket, a collection
|
|
23
|
+
* instanced inside another, or a factory prototype with no static address.
|
|
24
|
+
*
|
|
25
|
+
* Pure, like the indexes it feeds — the filesystem walk belongs to the caller.
|
|
26
|
+
* There is deliberately no everything-else-is-a-root fallback: that fallback is
|
|
27
|
+
* what offers a bare `/enemy` for an object living in another world, and losing
|
|
28
|
+
* a completion is safe where offering a false one is not.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildSceneCollectionRoles(input: SceneCollectionRolesInput): SceneCollectionRoles;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
import { type SceneMessage } from "./scene-text-format";
|
|
1
2
|
export interface SceneComponentIndex {
|
|
2
3
|
readonly ids: ReadonlySet<string>;
|
|
3
4
|
readonly incomplete: readonly string[];
|
|
4
5
|
}
|
|
6
|
+
export interface ComponentDeclarations {
|
|
7
|
+
readonly ids: Set<string>;
|
|
8
|
+
readonly resources: Map<string, string>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Every component id one already-parsed scene message declares, plus the
|
|
12
|
+
* resource each *referenced* component names, following the two `data:`
|
|
13
|
+
* payloads Defold embeds a whole document in. Exported because the game-object
|
|
14
|
+
* path walk attributes components to the object that owns them and must read
|
|
15
|
+
* the same rule this index reads — a second collector is exactly how the two
|
|
16
|
+
* universes drift apart.
|
|
17
|
+
*
|
|
18
|
+
* `blockName` names the block `message` was taken from, because the `data:`
|
|
19
|
+
* re-parse is keyed off it: a whole document is `""`, while a single
|
|
20
|
+
* `embedded_instances` block has to say so or its payload is never opened.
|
|
21
|
+
*/
|
|
22
|
+
export declare function collectComponentDeclarations(message: SceneMessage, displayPath: string, incomplete: string[], blockName?: string): ComponentDeclarations;
|
|
23
|
+
/** The id half of {@link collectComponentDeclarations}, for callers that join no resources. */
|
|
24
|
+
export declare function collectComponentIds(message: SceneMessage, displayPath: string, incomplete: string[], blockName?: string): Set<string>;
|
|
5
25
|
export declare function buildSceneComponentIndex(documents: ReadonlyMap<string, string>): SceneComponentIndex;
|
|
@@ -5,13 +5,16 @@ export interface SceneReadHost {
|
|
|
5
5
|
export declare const SCENE_EXTENSIONS: string[];
|
|
6
6
|
export declare const GUI_EXTENSIONS: string[];
|
|
7
7
|
export declare const INPUT_BINDING_EXTENSIONS: string[];
|
|
8
|
+
export declare const COLLECTION_REFERENCE_EXTENSIONS: string[];
|
|
8
9
|
export declare const ANIMATION_ASSET_EXTENSIONS: string[];
|
|
10
|
+
export declare const LIBRARY_SCENE_EXTENSIONS: string[];
|
|
9
11
|
export declare const PROJECT_EXTENSIONS: string[];
|
|
10
12
|
export declare const GAME_PROJECT_DOCUMENT = "game.project";
|
|
11
13
|
export declare function displayPathOf(projectRoot: string, filePath: string): string;
|
|
12
14
|
export declare function isExcludedProjectPath(displayPath: string): boolean;
|
|
13
15
|
export declare function readSceneDocuments(host: SceneReadHost, projectRoot: string, extensions?: readonly string[]): {
|
|
14
16
|
documents: Map<string, string>;
|
|
17
|
+
origins: Map<string, string>;
|
|
15
18
|
unreadable: string[];
|
|
16
19
|
paths: string[];
|
|
17
20
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SceneObjectPathIndex } from "./scene-object-path-index";
|
|
2
|
+
/**
|
|
3
|
+
* One place a script runs, and the addressing frame it runs in. At runtime an
|
|
4
|
+
* instance carries `m_CollectionPathHashState` seeded from its compiled id up
|
|
5
|
+
* to and including the last `/`, and `GetAbsoluteIdentifier` resolves a
|
|
6
|
+
* relative string by continuing that hash — so a relative address written in a
|
|
7
|
+
* script means one thing from this object and nothing anywhere else.
|
|
8
|
+
*/
|
|
9
|
+
export interface NamingContext {
|
|
10
|
+
/** The world-qualified composed path of the object hosting the script. */
|
|
11
|
+
readonly object: string;
|
|
12
|
+
/** The proxy socket this object lives behind, or `undefined` in the bootstrap world. */
|
|
13
|
+
readonly socket: string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* `object` through its last `/`, inclusive — the collection path a relative
|
|
16
|
+
* address continues. World-qualified by construction, so a relative address
|
|
17
|
+
* can never resolve across a world.
|
|
18
|
+
*/
|
|
19
|
+
readonly prefix: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The proxy socket an address names, or `undefined` when it names none — a
|
|
23
|
+
* bootstrap-world path, a relative path, a bare `#fragment`.
|
|
24
|
+
*
|
|
25
|
+
* Defold's address grammar is `[socket:][path][#fragment]` with both bracketed
|
|
26
|
+
* halves optional, so `mylevel:`, `mylevel:enemy` and `mylevel:#body` are as
|
|
27
|
+
* socket-qualified as `mylevel:/path`. The socket is therefore read from the
|
|
28
|
+
* `:` delimiter rather than from a following `/`: the head is the address up to
|
|
29
|
+
* the first `/` or `#` (the whole address when it carries neither), and the
|
|
30
|
+
* socket is the text before that head's first `:`. A `:` inside a path segment
|
|
31
|
+
* (`/level:1/enemy`) is not a socket, because the `/` closes the head first,
|
|
32
|
+
* and an empty socket (`:path`) is none.
|
|
33
|
+
*
|
|
34
|
+
* Exported because a written address literal and a composed object path are the
|
|
35
|
+
* same shape: the cross-world check reads a socket out of source text with this
|
|
36
|
+
* one rule rather than a second reader that could disagree with the contexts it
|
|
37
|
+
* compares against.
|
|
38
|
+
*/
|
|
39
|
+
export declare function socketOfAddress(address: string): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Every naming context each script resource runs in, keyed by that resource and
|
|
42
|
+
* sorted by object path.
|
|
43
|
+
*
|
|
44
|
+
* `guiScripts` maps a `.gui` display path to the gui-script resource it names,
|
|
45
|
+
* which the caller builds with `guiScriptResourceOf` — the walk needs no `.gui`
|
|
46
|
+
* documents of its own.
|
|
47
|
+
*
|
|
48
|
+
* A script hosted by several objects has several contexts, and that is not
|
|
49
|
+
* ambiguity: a `.go` instanced in two collections genuinely runs in both, and
|
|
50
|
+
* it is the consumer's provenance that tells an author which context an entry
|
|
51
|
+
* resolves from.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildScriptNamingContexts(index: SceneObjectPathIndex, guiScripts: ReadonlyMap<string, string>): ReadonlyMap<string, readonly NamingContext[]>;
|
|
54
|
+
/**
|
|
55
|
+
* The relative forms a script may write from one naming context: every object
|
|
56
|
+
* path under the context's prefix with that prefix stripped, and each of those
|
|
57
|
+
* joined to the components its object owns.
|
|
58
|
+
*
|
|
59
|
+
* An object `componentsOf` withheld still contributes its path — the object is
|
|
60
|
+
* provably there — but no `#component` join, because which components it owns
|
|
61
|
+
* was never read.
|
|
62
|
+
*/
|
|
63
|
+
export declare function relativeAddressesFrom(index: SceneObjectPathIndex, context: NamingContext): {
|
|
64
|
+
readonly paths: ReadonlySet<string>;
|
|
65
|
+
readonly addresses: ReadonlySet<string>;
|
|
66
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { SceneCollectionRoles } from "./scene-collection-roles";
|
|
1
2
|
export interface SceneObjectPathIndex {
|
|
2
3
|
readonly paths: ReadonlySet<string>;
|
|
3
4
|
readonly declaredIn: ReadonlyMap<string, readonly string[]>;
|
|
5
|
+
readonly componentsOf: ReadonlyMap<string, readonly string[]>;
|
|
6
|
+
readonly componentResourcesOf: ReadonlyMap<string, ReadonlyMap<string, string>>;
|
|
4
7
|
readonly incomplete: readonly string[];
|
|
5
8
|
}
|
|
6
|
-
export declare function buildSceneObjectPathIndex(documents: ReadonlyMap<string, string
|
|
9
|
+
export declare function buildSceneObjectPathIndex(documents: ReadonlyMap<string, string>, roles: SceneCollectionRoles): SceneObjectPathIndex;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
export declare function isAmbient(fileName: string): boolean;
|
|
3
|
+
export declare function canonicalHashSymbols(checker: ts.TypeChecker, program: ts.Program): ReadonlySet<ts.Symbol>;
|
|
4
|
+
/**
|
|
5
|
+
* The address text a slot expression is statically known to carry — a quoted or
|
|
6
|
+
* backtick-quoted literal's own text, or the string a `Hash`-typed value was
|
|
7
|
+
* hashed from — and `undefined` for everything a project only settles at
|
|
8
|
+
* runtime: a substituted template, a bare `Hash`, a `Url`, a `string`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function staticAddressTextOf(checker: ts.TypeChecker, node: ts.Expression, canonical: ReadonlySet<ts.Symbol>): string | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UrlParameterClass, UrlParameterTable } from "@defold-typescript/types";
|
|
1
|
+
import type { UrlParameterClass, UrlParameterEntry, UrlParameterTable } from "@defold-typescript/types";
|
|
2
2
|
import * as ts from "typescript";
|
|
3
3
|
export interface ClassifiedSlot {
|
|
4
4
|
readonly class: UrlParameterClass;
|
|
@@ -6,10 +6,12 @@ export interface ClassifiedSlot {
|
|
|
6
6
|
readonly textStart: number;
|
|
7
7
|
readonly fragmentStart: number;
|
|
8
8
|
readonly addressText?: string;
|
|
9
|
+
readonly nodeParameter?: string;
|
|
9
10
|
readonly resourceExtensions?: readonly string[];
|
|
10
11
|
}
|
|
11
12
|
export declare function isAddressClass(parameterClass: UrlParameterClass): boolean;
|
|
12
13
|
export declare function isFragmentCaret(slot: ClassifiedSlot, position: number): boolean;
|
|
14
|
+
export declare function addressEntryOfArgument(checker: ts.TypeChecker, table: UrlParameterTable, argument: ts.Expression): UrlParameterEntry | undefined;
|
|
13
15
|
export declare function addressClassOfArgument(checker: ts.TypeChecker, table: UrlParameterTable, argument: ts.Expression): UrlParameterClass;
|
|
14
16
|
export declare function resolveClassifiedSlotAtPosition(input: {
|
|
15
17
|
program: ts.Program;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { UrlParameterEntry, UrlParameterTable } from "@defold-typescript/types";
|
|
2
|
+
import * as ts from "typescript";
|
|
3
|
+
/** One address naming a world the script writing it cannot reach. */
|
|
4
|
+
export interface CrossWorldAddressFinding {
|
|
5
|
+
readonly fileName: string;
|
|
6
|
+
readonly start: number;
|
|
7
|
+
readonly length: number;
|
|
8
|
+
/** The statically-known address text, as written or as hashed. */
|
|
9
|
+
readonly address: string;
|
|
10
|
+
/** The foreign proxy socket that text names. */
|
|
11
|
+
readonly socket: string;
|
|
12
|
+
readonly message: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether this slot refuses a socket-qualified address naming another world.
|
|
16
|
+
*
|
|
17
|
+
* The negation of `slotAcceptsForeignSocket` in `@defold-typescript/types`, and
|
|
18
|
+
* deliberately fails open the same way: a slot carrying no judgment refuses
|
|
19
|
+
* nothing, so nothing is reported on a slot nobody classified.
|
|
20
|
+
*/
|
|
21
|
+
export declare function slotRejectsForeignSocket(entry: UrlParameterEntry): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Report every address-slot expression whose statically-known text names a proxy
|
|
24
|
+
* socket the script writing it does not run in, at a slot that resolves through
|
|
25
|
+
* an instance or component handle rather than the message bus.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately not folded into `checkUrlFragmentReachability`: that check
|
|
28
|
+
* suppresses index-wide on an incomplete component universe, while this one's
|
|
29
|
+
* honesty rule is per file — a script whose naming contexts could not be
|
|
30
|
+
* resolved is reported on at all, because unknown is not wrong. Fusing them
|
|
31
|
+
* would put one check's early return in front of the other's findings.
|
|
32
|
+
*
|
|
33
|
+
* `worldsOf` answers with the sockets of every naming context the file's script
|
|
34
|
+
* runs in; an empty answer is the unknown case and yields nothing.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkCrossWorldAddresses(input: {
|
|
37
|
+
program: ts.Program;
|
|
38
|
+
table: UrlParameterTable;
|
|
39
|
+
worldsOf: (fileName: string) => readonly (string | undefined)[];
|
|
40
|
+
sourceFiles?: readonly ts.SourceFile[];
|
|
41
|
+
}): readonly CrossWorldAddressFinding[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { UrlParameterTable } from "@defold-typescript/types";
|
|
2
2
|
import * as ts from "typescript";
|
|
3
3
|
import type { SceneComponentIndex } from "./scene-component-index";
|
|
4
|
+
import type { SceneObjectPathIndex } from "./scene-object-path-index";
|
|
4
5
|
export interface UrlFragmentFinding {
|
|
5
6
|
readonly fileName: string;
|
|
6
7
|
readonly start: number;
|
|
@@ -8,6 +9,7 @@ export interface UrlFragmentFinding {
|
|
|
8
9
|
readonly fragment: string;
|
|
9
10
|
readonly message: string;
|
|
10
11
|
}
|
|
12
|
+
export type SceneObjectComponents = Pick<SceneObjectPathIndex, "paths" | "componentsOf" | "incomplete">;
|
|
11
13
|
export type UrlFragmentReport = {
|
|
12
14
|
readonly kind: "suppressed";
|
|
13
15
|
readonly reasons: readonly string[];
|
|
@@ -19,5 +21,6 @@ export declare function checkUrlFragmentReachability(input: {
|
|
|
19
21
|
program: ts.Program;
|
|
20
22
|
table: UrlParameterTable;
|
|
21
23
|
index: SceneComponentIndex;
|
|
24
|
+
objects?: SceneObjectComponents;
|
|
22
25
|
sourceFiles?: readonly ts.SourceFile[];
|
|
23
26
|
}): UrlFragmentReport;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/transpiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@defold-typescript/types": "0.
|
|
38
|
+
"@defold-typescript/types": "0.32.0",
|
|
39
39
|
"@typescript-to-lua/language-extensions": "1.19.0",
|
|
40
40
|
"typescript-to-lua": "^1.36.0"
|
|
41
41
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { parseSceneTextFormat, type SceneMessage, SceneTextFormatError } from "./scene-text-format";
|
|
2
|
+
|
|
3
|
+
// Resource paths inside a scene are project-absolute; an index keyed off one
|
|
4
|
+
// drops the leading `/`, so a caller maps the file it holds forward through the
|
|
5
|
+
// build's output-path math and looks the result up.
|
|
6
|
+
export function resourceKey(resource: string): string {
|
|
7
|
+
return resource.startsWith("/") ? resource.slice(1) : resource;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// The keys a caller supplies name files on the host, so they may arrive with
|
|
11
|
+
// native separators; the resource paths they are matched against come out of
|
|
12
|
+
// scene content and are always `/`-separated. Normalizing here enforces that
|
|
13
|
+
// contract at the boundary instead of leaving it to whichever caller happens
|
|
14
|
+
// to build the maps.
|
|
15
|
+
export function normalizeDocumentKey(path: string): string {
|
|
16
|
+
return path.replaceAll("\\", "/");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SPRITE_SUFFIX = ".sprite";
|
|
20
|
+
const MODEL_SUFFIX = ".model";
|
|
21
|
+
const ANIMATION_SET_SUFFIX = ".animationset";
|
|
22
|
+
|
|
23
|
+
// The documents that carry only a hop to the ids, never the ids themselves: a
|
|
24
|
+
// `.sprite` names its tile source, a `.model` names its animation set, and a
|
|
25
|
+
// `.animationset` names the files whose basenames are the ids. Reading any of
|
|
26
|
+
// them for `animations { id: … }` yields an empty set that is indistinguishable
|
|
27
|
+
// from a real source declaring nothing.
|
|
28
|
+
const HOP_ONLY_SUFFIXES = [SPRITE_SUFFIX, MODEL_SUFFIX, ANIMATION_SET_SUFFIX];
|
|
29
|
+
|
|
30
|
+
function childrenOf(message: SceneMessage, name: string): readonly SceneMessage[] {
|
|
31
|
+
return message.messages.get(name) ?? [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Only `animations { id: … }` counts. Defold may also expose a bare
|
|
35
|
+
// `images { image: … }` entry as a one-frame animation named after the file,
|
|
36
|
+
// which is unverified here — and a wrong suggestion in an animation slot is a
|
|
37
|
+
// runtime crash rather than a no-op.
|
|
38
|
+
export function declaredAnimations(document: SceneMessage): Set<string> {
|
|
39
|
+
const ids = new Set<string>();
|
|
40
|
+
for (const animation of childrenOf(document, "animations")) {
|
|
41
|
+
for (const id of animation.fields.get("id") ?? []) {
|
|
42
|
+
if (id !== "") ids.add(id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return ids;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The animation ids every asset document declares, keyed by display path, with
|
|
49
|
+
// each unreadable document named in `unresolved` instead of silently declaring
|
|
50
|
+
// nothing. The hop-only kinds are skipped: they declare no ids of their own,
|
|
51
|
+
// only the path to the document that does, which their own readers parse them
|
|
52
|
+
// for.
|
|
53
|
+
//
|
|
54
|
+
// One reader for both animation indexes, so a sprite component and a gui
|
|
55
|
+
// texture can never disagree about what an atlas declares.
|
|
56
|
+
export function readAnimationSources(
|
|
57
|
+
assets: ReadonlyMap<string, string>,
|
|
58
|
+
unresolved: string[],
|
|
59
|
+
): Map<string, Set<string>> {
|
|
60
|
+
const animationsByTileSet = new Map<string, Set<string>>();
|
|
61
|
+
for (const [path, text] of assets) {
|
|
62
|
+
const displayPath = normalizeDocumentKey(path);
|
|
63
|
+
if (HOP_ONLY_SUFFIXES.some((suffix) => displayPath.endsWith(suffix))) continue;
|
|
64
|
+
let document: SceneMessage;
|
|
65
|
+
try {
|
|
66
|
+
document = parseSceneTextFormat(text);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (!(error instanceof SceneTextFormatError)) throw error;
|
|
69
|
+
unresolved.push(`${displayPath}: could not be parsed (${error.message})`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
animationsByTileSet.set(displayPath, declaredAnimations(document));
|
|
73
|
+
}
|
|
74
|
+
return animationsByTileSet;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The entry paths a `.animationset` lists, in document order. The values are
|
|
78
|
+
// resource paths, not ids: `AnimationSetBuilder` stamps the owning set's name
|
|
79
|
+
// on every animation a file contains, so the id an entry contributes is derived
|
|
80
|
+
// from its path rather than read out of the file it names. Empty values are
|
|
81
|
+
// dropped the way an empty animation id is.
|
|
82
|
+
export function animationSetEntries(document: SceneMessage): string[] {
|
|
83
|
+
const entries: string[] = [];
|
|
84
|
+
for (const entry of childrenOf(document, "animations")) {
|
|
85
|
+
for (const animation of entry.fields.get("animation") ?? []) {
|
|
86
|
+
if (animation !== "") entries.push(animation);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return entries;
|
|
90
|
+
}
|