@bloopjs/toodle 0.0.102 → 0.0.103

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.
@@ -4,14 +4,22 @@ import type { Limits } from "../limits";
4
4
  import { QuadNode } from "../scene/QuadNode";
5
5
  import type { SceneNode } from "../scene/SceneNode";
6
6
  import { TextShader } from "../text/TextShader";
7
+ import { Bundles } from "./Bundles";
7
8
  import type { AtlasBundleOpts, AtlasCoords, CpuTextureAtlas, TextureBundleOpts } from "./types";
8
9
  export type TextureId = string;
9
10
  export type BundleId = string;
10
11
  export type FontId = string;
12
+ export type AssetManagerOptions = {
13
+ /** Existing Bundles instance to use for CPU-side storage. If not provided, a new one is created. */
14
+ bundles?: Bundles;
15
+ /** Texture format (default: "rgba8unorm") */
16
+ format?: "rgba8unorm" | "rg8unorm";
17
+ };
11
18
  export declare class AssetManager {
12
19
  #private;
13
20
  readonly textureAtlas: GPUTexture;
14
- constructor(device: GPUDevice, presentationFormat: GPUTextureFormat, limits: Limits, format?: "rgba8unorm" | "rg8unorm");
21
+ readonly bundles: Bundles;
22
+ constructor(device: GPUDevice, presentationFormat: GPUTextureFormat, limits: Limits, options?: AssetManagerOptions);
15
23
  /**
16
24
  * True dimensions of a loaded texture, prior to any transparent pixel cropping.
17
25
  *
@@ -36,7 +44,7 @@ export declare class AssetManager {
36
44
  /**
37
45
  * A read-only map of all currently loaded textures.
38
46
  */
39
- get textures(): Map<string, AtlasCoords[]>;
47
+ get textures(): ReadonlyMap<string, AtlasCoords[]>;
40
48
  /**
41
49
  * A read-only array of all currently loaded texture ids.
42
50
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AssetManager.d.ts","sourceRoot":"","sources":["../../src/textures/AssetManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,eAAe,EAEf,iBAAiB,EAGlB,MAAM,SAAS,CAAC;AAGjB,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAQ5B,qBAAa,YAAY;;IACvB,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;gBAWhC,MAAM,EAAE,SAAS,EACjB,kBAAkB,EAAE,gBAAgB,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,YAAY,GAAG,UAAyB;IAwBlD;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAS5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAWnC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAUjC;;OAEG;IACH,IAAI,QAAQ,+BAEX;IAED;;OAEG;IACH,IAAI,UAAU,aAEb;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,YAAY,CAAC,IAAI,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtE;;;;;;;;;OASG;IACG,WAAW,CACf,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,GAAG,GAAG,WAAW,EACtB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;;;;IAwCtC;;;;;;;;OAQG;IACG,cAAc,CAClB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,iBAAiB,GAAG,eAAe,GACxC,OAAO,CAAC,QAAQ,CAAC;IAapB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,QAAQ;IAmBnC;;;;;OAKG;IACG,YAAY,CAAC,QAAQ,EAAE,QAAQ;IAqBrC;;;;;;OAMG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,SAAM;IAqB5D,OAAO,CAAC,EAAE,EAAE,MAAM;IASlB,wBAAwB,CAAC,IAAI,EAAE,SAAS,GAAG,QAAQ;IAqNnD;;OAEG;IACH,KAAK;sCAEyB,MAAM,EAAE;kCAKZ,MAAM,EAAE;QAMhC;;;;;;;WAOG;6BACkB,SAAS,UAAU,WAAW;QAWnD;;;;;WAKG;6BACkB,SAAS,KAAG,WAAW,EAAE;QAS9C;;;;;WAKG;+BACoB,SAAS,KAAG,IAAI;QAUvC;;;;WAIG;;YAGC;;;eAGG;;YAEH;;eAEG;;YAEH;;eAEG;;;QAKP;;;WAGG;;QAWH;;;;;WAKG;2BACsB,eAAe;QAyCxC;;;;WAIG;kCAC6B,MAAM;MActC;IAmCF;;OAEG;IACH,OAAO;CAGR"}
1
+ {"version":3,"file":"AssetManager.d.ts","sourceRoot":"","sources":["../../src/textures/AssetManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,eAAe,EACf,iBAAiB,EAElB,MAAM,SAAS,CAAC;AAGjB,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,MAAM,MAAM,mBAAmB,GAAG;IAChC,oGAAoG;IACpG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;CACpC,CAAC;AAEF,qBAAa,YAAY;;IACvB,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBASxB,MAAM,EAAE,SAAS,EACjB,kBAAkB,EAAE,gBAAgB,EACpC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB;IA2BnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAS5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAWnC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAUjC;;OAEG;IACH,IAAI,QAAQ,uCAEX;IAED;;OAEG;IACH,IAAI,UAAU,aAEb;IAED;;;;;;;;;;;;;;;;OAgBG;IACG,YAAY,CAAC,IAAI,EAAE,iBAAiB,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtE;;;;;;;;;OASG;IACG,WAAW,CACf,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,GAAG,GAAG,WAAW,EACtB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;;;;IAwCtC;;;;;;;;OAQG;IACG,cAAc,CAClB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,iBAAiB,GAAG,eAAe,GACxC,OAAO,CAAC,QAAQ,CAAC;IAapB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,QAAQ;IAsBnC;;;;;OAKG;IACG,YAAY,CAAC,QAAQ,EAAE,QAAQ;IAkBrC;;;;;;OAMG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,SAAM;IAqB5D,OAAO,CAAC,EAAE,EAAE,MAAM;IASlB,wBAAwB,CAAC,IAAI,EAAE,SAAS,GAAG,QAAQ;IA4FnD;;OAEG;IACH,KAAK;sCAEyB,MAAM,EAAE;kCAKZ,MAAM,EAAE;QAIhC;;;;;;;WAOG;6BACkB,SAAS,UAAU,WAAW;QAInD;;;;;WAKG;6BACkB,SAAS,KAAG,WAAW,EAAE;QAI9C;;;;;WAKG;+BACoB,SAAS,KAAG,IAAI;QAIvC;;;;WAIG;;YAGC;;;eAGG;;YAEH;;eAEG;;YAEH;;eAEG;;;QAKP;;;WAGG;;QAWH;;;;;WAKG;2BACsB,eAAe;QAsCxC;;;;WAIG;kCAC6B,MAAM;MAItC;IAmCF;;OAEG;IACH,OAAO;CAGR"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Bundles - A renderer-agnostic class for managing texture bundles and atlas coordinates.
3
+ *
4
+ * This class can be used standalone (without WebGPU) for:
5
+ * - Registering pre-baked texture atlases (Pixi/AssetPack format)
6
+ * - Looking up texture regions and UV coordinates
7
+ * - Managing bundle state
8
+ *
9
+ * For WebGPU rendering, use AssetManager which wraps this class and handles GPU operations.
10
+ */
11
+ import type { Size } from "../coreTypes/Size";
12
+ import type { Vec2 } from "../coreTypes/Vec2";
13
+ import type { AtlasBundleOpts, AtlasCoords, CpuTextureAtlas, TextureRegion } from "./types";
14
+ export type TextureId = string;
15
+ export type BundleId = string;
16
+ /**
17
+ * Options for creating a Bundles instance
18
+ */
19
+ export type BundlesOptions = {
20
+ /** The size of the texture atlas (default: 4096) */
21
+ atlasSize?: number;
22
+ };
23
+ /**
24
+ * Bundles manages texture bundle registration and atlas coordinate lookups.
25
+ *
26
+ * This is a pure TypeScript class with no WebGPU dependencies, suitable for
27
+ * use with custom renderers (e.g., WebGL fallbacks).
28
+ */
29
+ export declare class Bundles {
30
+ #private;
31
+ constructor(options?: BundlesOptions);
32
+ /**
33
+ * Register a bundle of pre-baked texture atlases.
34
+ *
35
+ * @param bundleId - Unique identifier for this bundle
36
+ * @param opts - Atlas bundle options containing atlas definitions
37
+ * @returns The bundle ID
38
+ */
39
+ registerAtlasBundle(bundleId: BundleId, opts: AtlasBundleOpts): Promise<BundleId>;
40
+ /**
41
+ * Register a bundle with pre-built CPU texture atlases.
42
+ * Used internally by AssetManager for texture bundles that require GPU packing.
43
+ *
44
+ * @param bundleId - Unique identifier for this bundle
45
+ * @param atlases - Pre-built CPU texture atlases
46
+ */
47
+ registerDynamicBundle(bundleId: BundleId, atlases: CpuTextureAtlas[]): void;
48
+ /**
49
+ * Check if a bundle is registered.
50
+ *
51
+ * @param bundleId - The bundle ID to check
52
+ */
53
+ hasBundle(bundleId: BundleId): boolean;
54
+ /**
55
+ * Check if a bundle is loaded.
56
+ *
57
+ * @param bundleId - The bundle ID to check
58
+ */
59
+ isBundleLoaded(bundleId: BundleId): boolean;
60
+ /**
61
+ * Get the atlas indices for a loaded bundle.
62
+ *
63
+ * @param bundleId - The bundle ID
64
+ * @returns Array of atlas indices, or empty array if not loaded
65
+ */
66
+ getBundleAtlasIndices(bundleId: BundleId): number[];
67
+ /**
68
+ * Mark a bundle as loaded without populating texture lookups.
69
+ * Used when texture lookups are already populated via loadAtlas.
70
+ *
71
+ * @param bundleId - The bundle to mark as loaded
72
+ * @param atlasIndices - Array of atlas indices, one per atlas
73
+ */
74
+ setBundleLoaded(bundleId: BundleId, atlasIndices: number[]): void;
75
+ /**
76
+ * Mark a bundle as loaded and populate texture lookups.
77
+ * For standalone usage (without AssetManager).
78
+ *
79
+ * @param bundleId - The bundle to mark as loaded
80
+ * @param atlasIndices - Array of atlas indices, one per atlas. If not provided, indices are auto-assigned sequentially.
81
+ */
82
+ markBundleLoaded(bundleId: BundleId, atlasIndices?: number[]): void;
83
+ /**
84
+ * Unmark a bundle as loaded and remove texture lookups.
85
+ *
86
+ * @param bundleId - The bundle to unload
87
+ */
88
+ unloadBundle(bundleId: BundleId): void;
89
+ /**
90
+ * A read-only map of all currently loaded textures.
91
+ */
92
+ get textures(): ReadonlyMap<TextureId, AtlasCoords[]>;
93
+ /**
94
+ * A read-only array of all currently loaded texture ids.
95
+ */
96
+ get textureIds(): TextureId[];
97
+ /**
98
+ * Get the atlas coordinates for a texture.
99
+ *
100
+ * @param id - The texture ID
101
+ * @returns Array of atlas coordinates (may have multiple if texture exists in multiple atlases)
102
+ */
103
+ getAtlasCoords(id: TextureId): AtlasCoords[];
104
+ /**
105
+ * Set the atlas coordinates for a texture.
106
+ * This allows for UV precision adjustments.
107
+ *
108
+ * @param id - The texture ID
109
+ * @param coords - The atlas coordinates to set
110
+ */
111
+ setAtlasCoords(id: TextureId, coords: AtlasCoords): void;
112
+ /**
113
+ * Add atlas coordinates for a texture entry.
114
+ * Used by AssetManager.loadAtlas for textures loaded outside of bundles.
115
+ *
116
+ * @param id - The texture ID
117
+ * @param coords - The atlas coordinates to add
118
+ */
119
+ addTextureEntry(id: TextureId, coords: AtlasCoords): void;
120
+ /**
121
+ * Remove texture entries for a specific atlas index.
122
+ * Used by AssetManager.unloadAtlas.
123
+ *
124
+ * @param atlasIndex - The atlas index to remove entries for
125
+ */
126
+ removeTextureEntriesForAtlas(atlasIndex: number): void;
127
+ /**
128
+ * Get the texture region (without atlas index) for a texture.
129
+ *
130
+ * @param id - The texture ID
131
+ * @returns The texture region, or undefined if not found
132
+ */
133
+ getTextureRegion(id: TextureId): TextureRegion | undefined;
134
+ /**
135
+ * Get the crop offset for a texture.
136
+ *
137
+ * @param id - The texture ID
138
+ * @returns The crop offset vector
139
+ */
140
+ getTextureOffset(id: TextureId): Vec2;
141
+ /**
142
+ * Get the original (uncropped) size of a texture.
143
+ *
144
+ * @param id - The texture ID
145
+ * @returns The original size in pixels
146
+ */
147
+ getSize(id: TextureId): Size;
148
+ /**
149
+ * Get the cropped size of a texture.
150
+ *
151
+ * @param id - The texture ID
152
+ * @returns The cropped size in pixels
153
+ */
154
+ getCroppedSize(id: TextureId): Size;
155
+ /**
156
+ * Check if a texture exists.
157
+ *
158
+ * @param id - The texture ID
159
+ * @returns True if the texture is registered
160
+ */
161
+ hasTexture(id: TextureId): boolean;
162
+ /**
163
+ * Get all registered bundle IDs.
164
+ */
165
+ getRegisteredBundleIds(): BundleId[];
166
+ /**
167
+ * Get all loaded bundle IDs.
168
+ */
169
+ getLoadedBundleIds(): BundleId[];
170
+ /**
171
+ * Get the CPU-side atlas data for a bundle.
172
+ * Useful for custom renderers that need access to the raw atlas data.
173
+ *
174
+ * @param bundleId - The bundle ID
175
+ * @returns Array of CPU texture atlases
176
+ */
177
+ getBundleAtlases(bundleId: BundleId): CpuTextureAtlas[];
178
+ /**
179
+ * The atlas size used for coordinate calculations.
180
+ */
181
+ get atlasSize(): number;
182
+ }
183
+ //# sourceMappingURL=Bundles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Bundles.d.ts","sourceRoot":"","sources":["../../src/textures/Bundles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,eAAe,EAEf,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAQ9B;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,OAAO;;gBAKN,OAAO,GAAE,cAAmB;IAIxC;;;;;;OAMG;IACG,mBAAmB,CACvB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,QAAQ,CAAC;IAmEpB;;;;;;OAMG;IACH,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI;IAQ3E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO;IAItC;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO;IAK3C;;;;;OAKG;IACH,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE;IAKnD;;;;;;OAMG;IACH,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI;IASjE;;;;;;OAMG;IACH,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI;IAwCnE;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IA8BtC;;OAEG;IACH,IAAI,QAAQ,IAAI,WAAW,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC,CAEpD;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,SAAS,EAAE,CAE5B;IAED;;;;;OAKG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,WAAW,EAAE;IAU5C;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IAWxD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IASzD;;;;;OAKG;IACH,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IActD;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS;IAQ1D;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAUrC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAS5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAYnC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO;IAIlC;;OAEG;IACH,sBAAsB,IAAI,QAAQ,EAAE;IAIpC;;OAEG;IACH,kBAAkB,IAAI,QAAQ,EAAE;IAMhC;;;;;;OAMG;IACH,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,eAAe,EAAE;IAQvD;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;CA0EF"}
@@ -1,2 +1,4 @@
1
+ export type { BundlesOptions } from "./Bundles";
2
+ export { Bundles } from "./Bundles";
1
3
  export type * from "./types";
2
4
  //# sourceMappingURL=mod.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/textures/mod.ts"],"names":[],"mappings":"AAAA,mBAAmB,SAAS,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/textures/mod.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,mBAAmB,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloopjs/toodle",
3
- "version": "0.0.102",
3
+ "version": "0.0.103",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
package/src/mod.ts CHANGED
@@ -20,3 +20,4 @@ export * as Textures from "./textures/mod";
20
20
  export * as Utils from "./utils/mod";
21
21
 
22
22
  export { AssetManager } from "./textures/AssetManager";
23
+ export { Bundles } from "./textures/Bundles";
@@ -8,14 +8,13 @@ import { FontPipeline } from "../text/FontPipeline";
8
8
  import { MsdfFont } from "../text/MsdfFont";
9
9
  import { TextShader } from "../text/TextShader";
10
10
  import { assert } from "../utils/mod";
11
+ import { Bundles } from "./Bundles";
11
12
  import { TextureComputeShader } from "./TextureComputeShader";
12
13
  import type {
13
14
  AtlasBundleOpts,
14
15
  AtlasCoords,
15
16
  CpuTextureAtlas,
16
- PixiRegion,
17
17
  TextureBundleOpts,
18
- TextureRegion,
19
18
  TextureWithMetadata,
20
19
  } from "./types";
21
20
  import { getBitmapFromUrl, packBitmapsToAtlas } from "./util";
@@ -24,18 +23,18 @@ export type TextureId = string;
24
23
  export type BundleId = string;
25
24
  export type FontId = string;
26
25
 
27
- type Bundle = {
28
- atlases: CpuTextureAtlas[];
29
- isLoaded: boolean;
30
- atlasIndices: number[];
26
+ export type AssetManagerOptions = {
27
+ /** Existing Bundles instance to use for CPU-side storage. If not provided, a new one is created. */
28
+ bundles?: Bundles;
29
+ /** Texture format (default: "rgba8unorm") */
30
+ format?: "rgba8unorm" | "rg8unorm";
31
31
  };
32
32
 
33
33
  export class AssetManager {
34
34
  readonly textureAtlas: GPUTexture;
35
+ readonly bundles: Bundles;
35
36
  #device: GPUDevice;
36
37
  #presentationFormat: GPUTextureFormat;
37
- #bundles: Map<BundleId, Bundle> = new Map();
38
- #textures: Map<string, AtlasCoords[]> = new Map();
39
38
  #fonts: Map<string, TextShader> = new Map();
40
39
  #cropComputeShader: TextureComputeShader;
41
40
  #limits: Limits;
@@ -45,11 +44,14 @@ export class AssetManager {
45
44
  device: GPUDevice,
46
45
  presentationFormat: GPUTextureFormat,
47
46
  limits: Limits,
48
- format: "rgba8unorm" | "rg8unorm" = "rgba8unorm",
47
+ options: AssetManagerOptions = {},
49
48
  ) {
50
49
  this.#device = device;
51
50
  this.#presentationFormat = presentationFormat;
52
51
  this.#limits = limits;
52
+ this.bundles =
53
+ options.bundles ?? new Bundles({ atlasSize: limits.textureSize });
54
+ const format = options.format ?? "rgba8unorm";
53
55
  this.textureAtlas = device.createTexture({
54
56
  label: "Asset Manager Atlas Texture",
55
57
  size: [
@@ -108,27 +110,27 @@ export class AssetManager {
108
110
  * @returns Whether the image has been cropped (i.e. if it has uvScaledCropped)
109
111
  */
110
112
  isCropped(id: TextureId): boolean {
111
- if (!this.#textures.has(id)) {
113
+ if (!this.bundles.hasTexture(id)) {
112
114
  throw new Error(
113
115
  `Texture ${id} not found in atlas. Have you called toodle.loadTextures with this id or toodle.loadBundle with a bundle that contains it?`,
114
116
  );
115
117
  }
116
118
 
117
- return this.#textures.get(id)![0].uvScaleCropped === undefined;
119
+ return this.bundles.getAtlasCoords(id)[0].uvScaleCropped === undefined;
118
120
  }
119
121
 
120
122
  /**
121
123
  * A read-only map of all currently loaded textures.
122
124
  */
123
125
  get textures() {
124
- return this.#textures;
126
+ return this.bundles.textures;
125
127
  }
126
128
 
127
129
  /**
128
130
  * A read-only array of all currently loaded texture ids.
129
131
  */
130
132
  get textureIds() {
131
- return Array.from(this.#textures.keys());
133
+ return this.bundles.textureIds;
132
134
  }
133
135
 
134
136
  /**
@@ -200,7 +202,7 @@ export class AssetManager {
200
202
  atlasIndex,
201
203
  };
202
204
 
203
- this.#textures.set(id, [coords]);
205
+ this.bundles.addTextureEntry(id, coords);
204
206
  this.#availableIndices.delete(atlasIndex);
205
207
 
206
208
  textureWrapper.texture.destroy();
@@ -238,22 +240,25 @@ export class AssetManager {
238
240
  * See: https://toodle.gg/f849595b3ed13fc956fc1459a5cb5f0228f9d259/examples/texture-bundles.html
239
241
  */
240
242
  async loadBundle(bundleId: BundleId) {
241
- const bundle = this.#bundles.get(bundleId);
242
- if (!bundle) {
243
+ if (!this.bundles.hasBundle(bundleId)) {
243
244
  throw new Error(`Bundle ${bundleId} not found`);
244
245
  }
245
246
 
246
- if (bundle.isLoaded) {
247
+ if (this.bundles.isBundleLoaded(bundleId)) {
247
248
  console.warn(`Bundle ${bundleId} is already loaded.`);
248
249
  return;
249
250
  }
250
251
 
251
- for (const atlas of bundle.atlases) {
252
+ const atlases = this.bundles.getBundleAtlases(bundleId);
253
+ const atlasIndices: number[] = [];
254
+
255
+ for (const atlas of atlases) {
252
256
  const atlasIndex = await this.extra.loadAtlas(atlas);
253
- bundle.atlasIndices.push(atlasIndex);
257
+ atlasIndices.push(atlasIndex);
254
258
  }
255
259
 
256
- bundle.isLoaded = true;
260
+ // Use setBundleLoaded (not markBundleLoaded) since loadAtlas already populated textures
261
+ this.bundles.setBundleLoaded(bundleId, atlasIndices);
257
262
  }
258
263
 
259
264
  /**
@@ -263,24 +268,21 @@ export class AssetManager {
263
268
  * @param bundleId - The id of the bundle to unload
264
269
  */
265
270
  async unloadBundle(bundleId: BundleId) {
266
- const bundle = this.#bundles.get(bundleId);
267
- if (!bundle) {
271
+ if (!this.bundles.hasBundle(bundleId)) {
268
272
  throw new Error(`Bundle ${bundleId} not found`);
269
273
  }
270
274
 
271
- if (!bundle.isLoaded) {
275
+ if (!this.bundles.isBundleLoaded(bundleId)) {
272
276
  console.warn(`Bundle ${bundleId} is not loaded.`);
273
277
  return;
274
278
  }
275
279
 
280
+ const atlasIndices = this.bundles.getBundleAtlasIndices(bundleId);
276
281
  await Promise.all(
277
- bundle.atlasIndices.map((atlasIndex) =>
278
- this.extra.unloadAtlas(atlasIndex),
279
- ),
282
+ atlasIndices.map((atlasIndex) => this.extra.unloadAtlas(atlasIndex)),
280
283
  );
281
284
 
282
- bundle.isLoaded = false;
283
- bundle.atlasIndices = [];
285
+ this.bundles.unloadBundle(bundleId);
284
286
  }
285
287
 
286
288
  /**
@@ -328,15 +330,13 @@ export class AssetManager {
328
330
  )
329
331
  return;
330
332
 
331
- const coords: AtlasCoords[] | undefined = this.#textures.get(
332
- node.textureId,
333
- );
334
- if (!coords || !coords.length) {
333
+ if (!this.bundles.hasTexture(node.textureId)) {
335
334
  throw new Error(
336
335
  `Node ${node.id} references an invalid texture ${node.textureId}.`,
337
336
  );
338
337
  }
339
338
 
339
+ const coords = this.bundles.getAtlasCoords(node.textureId);
340
340
  if (
341
341
  coords.find((coord) => coord.atlasIndex === node.atlasCoords.atlasIndex)
342
342
  )
@@ -345,22 +345,6 @@ export class AssetManager {
345
345
  node.extra.setAtlasCoords(coords[0]);
346
346
  }
347
347
 
348
- /**
349
- * Sets a designated texture ID to the corresponding `AtlasRegion` built from a `TextureRegion` and `numerical atlas index.
350
- * @param id - `String` representing the texture name. I.e. "PlayerSprite"
351
- * @param textureRegion - `TextureRegion` corresponding the uv and texture offsets
352
- * @param atlasIndex - `number` of the atlas that the texture will live in.
353
- * @private
354
- */
355
- #addTexture(id: string, textureRegion: TextureRegion, atlasIndex: number) {
356
- this.#textures.set(id, [
357
- {
358
- ...textureRegion,
359
- atlasIndex,
360
- },
361
- ]);
362
- }
363
-
364
348
  /**
365
349
  *
366
350
  * @param bitmap - `ImageBitmap` to be processed into a `GPUTexture` for storage and manipulation
@@ -422,115 +406,12 @@ export class AssetManager {
422
406
  this.#device,
423
407
  );
424
408
 
425
- this.#bundles.set(bundleId, {
426
- atlases,
427
- atlasIndices: [],
428
- isLoaded: false,
429
- });
409
+ this.bundles.registerDynamicBundle(bundleId, atlases);
430
410
  }
431
411
 
432
412
  async #registerBundleFromAtlases(bundleId: BundleId, opts: AtlasBundleOpts) {
433
- const atlases: CpuTextureAtlas[] = [];
434
-
435
- for (const atlas of opts.atlases) {
436
- const jsonUrl =
437
- atlas.json ??
438
- new URL(
439
- atlas.png!.toString().replace(".png", ".json"),
440
- atlas.png!.origin,
441
- );
442
- const pngUrl =
443
- atlas.png ??
444
- new URL(
445
- atlas.json!.toString().replace(".json", ".png"),
446
- atlas.json!.origin,
447
- );
448
-
449
- const atlasDef = await (await fetch(jsonUrl)).json();
450
- const bitmap = !opts.rg8
451
- ? await getBitmapFromUrl(pngUrl)
452
- : await createImageBitmap(new ImageData(1, 1)); // placeholder bitmap if using rg8
453
-
454
- let rg8Bytes: Uint8Array<ArrayBuffer> | undefined;
455
- if (opts.rg8) {
456
- const rg8url = new URL(
457
- pngUrl.toString().replace(".png", ".rg8.gz"),
458
- pngUrl.origin,
459
- );
460
- const rgBytes = await fetch(rg8url).then(async (r) => {
461
- const enc = (r.headers.get("content-encoding") || "").toLowerCase();
462
- // If server/CDN already set Content-Encoding, Fetch returns decompressed bytes.
463
- if (
464
- enc.includes("gzip") ||
465
- enc.includes("br") ||
466
- enc.includes("deflate")
467
- ) {
468
- return new Uint8Array(await r.arrayBuffer());
469
- }
470
-
471
- assert(r.body, "Response body of rg8 file is null");
472
- const ds = new DecompressionStream("gzip");
473
- const ab = await new Response(r.body.pipeThrough(ds)).arrayBuffer();
474
- return new Uint8Array(ab);
475
- });
476
- rg8Bytes = rgBytes;
477
- }
478
-
479
- const cpuTextureAtlas: CpuTextureAtlas = {
480
- texture: bitmap,
481
- rg8Bytes,
482
- textureRegions: new Map(),
483
- width: opts.rg8 ? this.#limits.textureSize : bitmap.width,
484
- height: opts.rg8 ? this.#limits.textureSize : bitmap.height,
485
- };
486
-
487
- for (const [assetId, frame] of Object.entries(atlasDef.frames) as [
488
- string,
489
- PixiRegion,
490
- ][]) {
491
- const leftCrop = frame.spriteSourceSize.x;
492
- const rightCrop =
493
- frame.sourceSize.w -
494
- frame.spriteSourceSize.x -
495
- frame.spriteSourceSize.w;
496
- const topCrop = frame.spriteSourceSize.y;
497
- const bottomCrop =
498
- frame.sourceSize.h -
499
- frame.spriteSourceSize.y -
500
- frame.spriteSourceSize.h;
501
-
502
- cpuTextureAtlas.textureRegions.set(assetId, {
503
- cropOffset: {
504
- x: leftCrop - rightCrop,
505
- y: bottomCrop - topCrop,
506
- },
507
- originalSize: {
508
- width: frame.sourceSize.w,
509
- height: frame.sourceSize.h,
510
- },
511
- uvOffset: {
512
- x: frame.frame.x / cpuTextureAtlas.width,
513
- y: frame.frame.y / cpuTextureAtlas.height,
514
- },
515
- uvScale: {
516
- width: frame.sourceSize.w / cpuTextureAtlas.width,
517
- height: frame.sourceSize.h / cpuTextureAtlas.height,
518
- },
519
- uvScaleCropped: {
520
- width: frame.frame.w / cpuTextureAtlas.width,
521
- height: frame.frame.h / cpuTextureAtlas.height,
522
- },
523
- });
524
- }
525
-
526
- atlases.push(cpuTextureAtlas);
527
- }
528
-
529
- this.#bundles.set(bundleId, {
530
- atlases,
531
- atlasIndices: [],
532
- isLoaded: false,
533
- });
413
+ // Delegate to the Bundles instance for atlas parsing
414
+ await this.bundles.registerAtlasBundle(bundleId, opts);
534
415
  }
535
416
 
536
417
  /**
@@ -539,14 +420,12 @@ export class AssetManager {
539
420
  extra = {
540
421
  // Get an array of all currently registered bundle ids.
541
422
  getRegisteredBundleIds: (): string[] => {
542
- return this.#bundles ? Array.from(this.#bundles.keys()) : [];
423
+ return this.bundles.getRegisteredBundleIds();
543
424
  },
544
425
 
545
426
  // Get an array of all currently loaded bundle ids.
546
427
  getLoadedBundleIds: (): string[] => {
547
- return Array.from(this.#bundles.entries())
548
- .filter(([, value]) => value.isLoaded)
549
- .map(([key]) => key);
428
+ return this.bundles.getLoadedBundleIds();
550
429
  },
551
430
 
552
431
  /**
@@ -558,14 +437,7 @@ export class AssetManager {
558
437
  * @param coords - The atlas coordinates to set
559
438
  */
560
439
  setAtlasCoords: (id: TextureId, coords: AtlasCoords) => {
561
- const oldCoords: AtlasCoords[] | undefined = this.#textures.get(id);
562
- if (!oldCoords) return;
563
- const indexToModify = oldCoords.findIndex(
564
- (coord) => coord.atlasIndex === coords.atlasIndex,
565
- );
566
- if (indexToModify === -1) return;
567
- oldCoords[indexToModify] = coords;
568
- this.#textures.set(id, oldCoords);
440
+ this.bundles.setAtlasCoords(id, coords);
569
441
  },
570
442
 
571
443
  /**
@@ -575,12 +447,7 @@ export class AssetManager {
575
447
  * @returns An array of the atlas coordinates for the texture
576
448
  */
577
449
  getAtlasCoords: (id: TextureId): AtlasCoords[] => {
578
- if (!this.#textures.has(id)) {
579
- throw new Error(
580
- `Texture ${id} not found in atlas. Have you called toodle.loadBundle with a bundle that contains this id (or toodle.loadTextures with this id as a key)?`,
581
- );
582
- }
583
- return this.#textures.get(id) ?? [];
450
+ return this.bundles.getAtlasCoords(id);
584
451
  },
585
452
 
586
453
  /**
@@ -590,13 +457,7 @@ export class AssetManager {
590
457
  * @returns Point of the texture's associated X,Y offset
591
458
  */
592
459
  getTextureOffset: (id: TextureId): Vec2 => {
593
- const texture: AtlasCoords[] | undefined = this.#textures.get(id);
594
- if (!texture) {
595
- throw new Error(
596
- `Texture ${id} not found in atlas. Have you called toodle.loadTextures with this id or toodle.loadBundle with a bundle that contains it?`,
597
- );
598
- }
599
- return texture[0].cropOffset;
460
+ return this.bundles.getTextureOffset(id);
600
461
  },
601
462
 
602
463
  /**
@@ -675,10 +536,7 @@ export class AssetManager {
675
536
  }
676
537
 
677
538
  for (const [id, region] of atlas.textureRegions) {
678
- const existing = this.#textures.get(id);
679
- if (existing) {
680
- existing.push({ ...region, atlasIndex });
681
- } else this.#addTexture(id, region, atlasIndex);
539
+ this.bundles.addTextureEntry(id, { ...region, atlasIndex });
682
540
  }
683
541
  return atlasIndex;
684
542
  },
@@ -690,17 +548,7 @@ export class AssetManager {
690
548
  */
691
549
  unloadAtlas: async (atlasIndex: number) => {
692
550
  this.#availableIndices.add(atlasIndex);
693
- for (const [id, coords] of this.#textures.entries()) {
694
- const indexToModify = coords.findIndex(
695
- (coord) => coord.atlasIndex === atlasIndex,
696
- );
697
- if (indexToModify !== -1) {
698
- coords.splice(indexToModify, 1);
699
- }
700
- if (!coords.length) {
701
- this.#textures.delete(id);
702
- }
703
- }
551
+ this.bundles.removeTextureEntriesForAtlas(atlasIndex);
704
552
  },
705
553
  };
706
554