1
+ import { CLUSTER_BOUNDS_SHADER_ASSET_UUID , CLUSTER_LIGHTS_SHADER_ASSET_UUID } from "../../../../src/mod.js" ;
2
+ import { ProjectAssetTypeJavascript } from "../../assets/projectAssetType/ProjectAssetTypeJavascript.js" ;
1
3
import { createTreeViewStructure } from "../../ui/propertiesTreeView/createStructureHelpers.js" ;
2
4
import { Task } from "./Task.js" ;
3
5
@@ -17,6 +19,10 @@ import {Task} from "./Task.js";
17
19
* asset uuids that are expected to be loaded. The task uses this list to determine
18
20
* which asset loader types should be included. This way, loader types that are
19
21
* not needed are not included when bundling with tree shaking enabled.
22
+ * @property {import("../../../../src/mod.js").UuidString[] } entryPoints A list of
23
+ * JavaScript asset uuids that will be using the generated services file. It is used to
24
+ * analyse which services are actually used so that any unused services can be omitted
25
+ * from the generation.
20
26
*/
21
27
22
28
/**
@@ -56,12 +62,27 @@ import {Task} from "./Task.js";
56
62
* to the current ProjectAssetType config.
57
63
*/
58
64
59
- /** @extends {Task<TaskGenerateServicesConfig> } */
65
+ /**
66
+ * @typedef TaskGenerateServicesCustomData
67
+ * @property {import("../../../../src/mod.js").UuidString[] } usedAssets
68
+ */
69
+
70
+ /** @extends {Task<TaskGenerateServicesConfig, TaskGenerateServicesCustomData> } */
60
71
export class TaskGenerateServices extends Task {
61
72
static uiName = "Generate Services" ;
62
73
static type = "renda:generateServices" ;
63
74
64
75
static configStructure = createTreeViewStructure ( {
76
+ entryPoints : {
77
+ type : "array" ,
78
+ tooltip : "A list of script files that is checked for usage of the generated services. Some parts of the generated services script are omitted depending on usage, in order to reduce bundle size." ,
79
+ guiOpts : {
80
+ arrayType : "droppable" ,
81
+ arrayGuiOpts : {
82
+ supportedAssetTypes : [ ProjectAssetTypeJavascript ] ,
83
+ } ,
84
+ } ,
85
+ } ,
65
86
outputLocation : {
66
87
type : "array" ,
67
88
guiOpts : {
@@ -70,13 +91,15 @@ export class TaskGenerateServices extends Task {
70
91
} ,
71
92
usedAssets : {
72
93
type : "array" ,
94
+ tooltip : "A list of assets that are expected to be loaded. Unused asset types are stripped from the asset loader in order to reduce bundle size." ,
73
95
guiOpts : {
74
96
arrayType : "droppable" ,
75
97
} ,
76
98
} ,
77
99
} ) ;
78
100
79
101
/**
102
+ * @override
80
103
* @param {import("./Task.js").RunTaskOptions<TaskGenerateServicesConfig> } options
81
104
*/
82
105
async runTask ( { config} ) {
@@ -89,20 +112,27 @@ export class TaskGenerateServices extends Task {
89
112
throw new Error ( "Failed to run task: no asset manager." ) ;
90
113
}
91
114
92
- /** @type {Map<import("../../assets/projectAssetType/ProjectAssetType.js").ProjectAssetTypeIdentifier, Set<import("../../assets/ProjectAsset.js").ProjectAssetAny>> } */
93
- const assetTypes = new Map ( ) ;
94
- for await ( const uuid of assetManager . collectAllAssetReferences ( config . usedAssets ) ) {
95
- const asset = await assetManager . getProjectAssetFromUuid ( uuid ) ;
96
- if ( asset ?. assetType ) {
97
- let assetsSet = assetTypes . get ( asset . assetType ) ;
98
- if ( ! assetsSet ) {
99
- assetsSet = new Set ( ) ;
100
- assetTypes . set ( asset . assetType , assetsSet ) ;
101
- }
102
- assetsSet . add ( asset ) ;
115
+ let entryPointContents = "" ;
116
+ for ( const entryPoint of config . entryPoints ) {
117
+ const asset = await assetManager . getProjectAssetFromUuid ( entryPoint , {
118
+ assertAssetType : ProjectAssetTypeJavascript ,
119
+ } ) ;
120
+ if ( asset ) {
121
+ const content = await asset . readAssetData ( ) ;
122
+ entryPointContents += content ;
103
123
}
104
124
}
105
125
126
+ // TODO: This is a pretty primitive way of figuring out which services are needed, since we are
127
+ // essentially just looking if some strings of text appear in one of the entry points.
128
+ // Ideally we would parse the ast and figure out what is being used that way.
129
+ // It would add a lot of complexity and wouldn't be able to handle all cases, but right now
130
+ // if large files are used as entry points, the chance of these strings existing a pretty big so
131
+ // there could be a lot of false positives.
132
+ let needsAssetLoader = entryPointContents . includes ( "assetLoader" ) ;
133
+ let needsEngineAssetsManager = entryPointContents . includes ( "engineAssetsManager" ) ;
134
+ const needsRenderer = entryPointContents . includes ( "renderer" ) ;
135
+
106
136
/** @type {Map<string, Set<string>> } */
107
137
const collectedImports = new Map ( ) ;
108
138
/**
@@ -118,6 +148,14 @@ export class TaskGenerateServices extends Task {
118
148
identifiers . add ( identifier ) ;
119
149
}
120
150
151
+ /**
152
+ * A list of asset uuids that the engine requires to function.
153
+ * Assets in this list will automatically get bundled when the services
154
+ * script is generated as part of the 'build application' task.
155
+ * @type {Set<import("../../../../src/mod.js").UuidString> }
156
+ */
157
+ const usedAssets = new Set ( ) ;
158
+
121
159
/**
122
160
* @typedef CollectedAssetLoaderType
123
161
* @property {string } instanceIdentifier
@@ -131,30 +169,52 @@ export class TaskGenerateServices extends Task {
131
169
/** @type {Set<string> } */
132
170
const collectedExportIdentifiers = new Set ( ) ;
133
171
134
- for ( const [ assetTypeIdentifier , assets ] of assetTypes ) {
135
- const assetType = this . editorInstance . projectAssetTypeManager . getAssetType ( assetTypeIdentifier ) ;
136
- const config = assetType ?. assetLoaderTypeImportConfig ;
137
- if ( config ) {
138
- const moduleSpecifier = config . moduleSpecifier || "renda" ;
139
- addImport ( config . identifier , moduleSpecifier ) ;
140
- let extra = "" ;
141
- if ( config . extra ) {
142
- extra = await config . extra ( {
143
- editor : this . editorInstance ,
144
- addImport,
145
- usedAssets : Array . from ( assets ) ,
172
+ if ( needsAssetLoader ) {
173
+ /** @type {Map<import("../../assets/projectAssetType/ProjectAssetType.js").ProjectAssetTypeIdentifier, Set<import("../../assets/ProjectAsset.js").ProjectAssetAny>> } */
174
+ const assetTypes = new Map ( ) ;
175
+ for await ( const uuid of assetManager . collectAllAssetReferences ( config . usedAssets ) ) {
176
+ const asset = await assetManager . getProjectAssetFromUuid ( uuid ) ;
177
+ if ( asset ?. assetType ) {
178
+ let assetsSet = assetTypes . get ( asset . assetType ) ;
179
+ if ( ! assetsSet ) {
180
+ assetsSet = new Set ( ) ;
181
+ assetTypes . set ( asset . assetType , assetsSet ) ;
182
+ }
183
+ assetsSet . add ( asset ) ;
184
+ }
185
+ }
186
+
187
+ for ( const [ assetTypeIdentifier , assets ] of assetTypes ) {
188
+ const assetType = this . editorInstance . projectAssetTypeManager . getAssetType ( assetTypeIdentifier ) ;
189
+ const config = assetType ?. assetLoaderTypeImportConfig ;
190
+ if ( config ) {
191
+ const moduleSpecifier = config . moduleSpecifier || "renda" ;
192
+ addImport ( config . identifier , moduleSpecifier ) ;
193
+ let extra = "" ;
194
+ if ( config . extra ) {
195
+ extra = await config . extra ( {
196
+ editor : this . editorInstance ,
197
+ addImport,
198
+ usedAssets : Array . from ( assets ) ,
199
+ } ) ;
200
+ }
201
+ collectedAssetLoaderTypes . set ( config . identifier , {
202
+ instanceIdentifier : config . instanceIdentifier || "" ,
203
+ extra,
204
+ returnInstanceIdentifier : config . returnInstanceIdentifier ?? true ,
146
205
} ) ;
147
206
}
148
- collectedAssetLoaderTypes . set ( config . identifier , {
149
- instanceIdentifier : config . instanceIdentifier || "" ,
150
- extra,
151
- returnInstanceIdentifier : config . returnInstanceIdentifier ?? true ,
152
- } ) ;
153
207
}
154
208
}
155
209
156
- const needsAssetLoader = collectedAssetLoaderTypes . size > 0 ;
157
-
210
+ if ( needsRenderer ) {
211
+ addImport ( "WebGpuRenderer" , "renda" ) ;
212
+ needsEngineAssetsManager = true ;
213
+ }
214
+ if ( needsEngineAssetsManager ) {
215
+ addImport ( "EngineAssetsManager" , "renda" ) ;
216
+ needsAssetLoader = true ;
217
+ }
158
218
if ( needsAssetLoader ) {
159
219
addImport ( "AssetLoader" , "renda" ) ;
160
220
}
@@ -169,6 +229,7 @@ export class TaskGenerateServices extends Task {
169
229
170
230
// Services instantiation
171
231
code += "export function initializeServices() {\n" ;
232
+
172
233
if ( needsAssetLoader ) {
173
234
code += " const assetLoader = new AssetLoader();\n" ;
174
235
code += "\n" ;
@@ -193,12 +254,25 @@ export class TaskGenerateServices extends Task {
193
254
}
194
255
}
195
256
257
+ if ( needsEngineAssetsManager ) {
258
+ code += "const engineAssetsManager = new EngineAssetsManager(assetLoader);" ;
259
+ collectedExportIdentifiers . add ( "engineAssetsManager" ) ;
260
+ }
261
+
262
+ if ( needsRenderer ) {
263
+ code += "const renderer = new WebGpuRenderer(engineAssetsManager);" ;
264
+ code += "renderer.init();" ;
265
+ collectedExportIdentifiers . add ( "renderer" ) ;
266
+ usedAssets . add ( CLUSTER_BOUNDS_SHADER_ASSET_UUID ) ;
267
+ usedAssets . add ( CLUSTER_LIGHTS_SHADER_ASSET_UUID ) ;
268
+ }
269
+
196
270
// Return object
197
271
code += ` return {${ Array . from ( collectedExportIdentifiers ) . join ( ", " ) } };\n` ;
198
272
199
273
code += "}\n" ;
200
274
201
- /** @type {import("./Task.js").RunTaskReturn } */
275
+ /** @type {import("./Task.js").RunTaskReturn<TaskGenerateServicesCustomData> } */
202
276
const result = {
203
277
writeAssets : [
204
278
{
@@ -207,6 +281,9 @@ export class TaskGenerateServices extends Task {
207
281
fileData : code ,
208
282
} ,
209
283
] ,
284
+ customData : {
285
+ usedAssets : Array . from ( usedAssets ) ,
286
+ } ,
210
287
} ;
211
288
return result ;
212
289
}
0 commit comments