@babylonjs/inspector 9.3.0 → 9.3.1

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.
@@ -8034,7 +8034,7 @@ class ExtensionManager {
8034
8034
  // Register the ServiceDefinitions.
8035
8035
  let servicesRegistrationToken = null;
8036
8036
  if (installedExtension.extensionModule.default.serviceDefinitions) {
8037
- servicesRegistrationToken = await this._serviceContainer.addServicesAsync(...installedExtension.extensionModule.default.serviceDefinitions);
8037
+ servicesRegistrationToken = this._serviceContainer.addServices(...installedExtension.extensionModule.default.serviceDefinitions);
8038
8038
  }
8039
8039
  // Create a registration token to for dispose.
8040
8040
  installedExtension.registrationToken = {
@@ -8155,17 +8155,15 @@ class ServiceContainer {
8155
8155
  _parent?._children.add(this);
8156
8156
  }
8157
8157
  /**
8158
- * Adds a set of service definitions in the service container.
8158
+ * Adds a set of service definitions to the service container.
8159
8159
  * The services are sorted based on their dependencies.
8160
- * @param args The service definitions to register, and optionally an abort signal.
8161
- * @returns A disposable that will remove the service definition from the service container.
8160
+ * @param serviceDefinitions The service definitions to register.
8161
+ * @returns A disposable that will remove the service definitions from the service container.
8162
8162
  */
8163
- async addServicesAsync(...args) {
8163
+ addServices(...serviceDefinitions) {
8164
8164
  if (this._isDisposed) {
8165
8165
  throw new Error("ServiceContainer is disposed.");
8166
8166
  }
8167
- const abortSignal = args[args.length - 1] instanceof AbortSignal ? args.pop() : undefined;
8168
- const serviceDefinitions = args;
8169
8167
  const sortedServiceDefinitions = SortServiceDefinitions(serviceDefinitions);
8170
8168
  const dispose = () => {
8171
8169
  for (const serviceDefinition of sortedServiceDefinitions.reverse()) {
@@ -8174,9 +8172,7 @@ class ServiceContainer {
8174
8172
  };
8175
8173
  try {
8176
8174
  for (const serviceDefinition of sortedServiceDefinitions) {
8177
- // We could possibly optimize this by allowing some parallel initialization of services, but this would be way more complex, so let's wait and see if it's needed.
8178
- // eslint-disable-next-line no-await-in-loop
8179
- await this._addServiceAsync(serviceDefinition, abortSignal);
8175
+ this._addService(serviceDefinition);
8180
8176
  }
8181
8177
  }
8182
8178
  catch (error) {
@@ -8190,18 +8186,12 @@ class ServiceContainer {
8190
8186
  /**
8191
8187
  * Registers a service definition in the service container.
8192
8188
  * @param serviceDefinition The service definition to register.
8193
- * @param abortSignal An optional abort signal.
8194
8189
  * @returns A disposable that will remove the service definition from the service container.
8195
8190
  */
8196
- async addServiceAsync(serviceDefinition, abortSignal) {
8197
- if (abortSignal) {
8198
- return await this.addServicesAsync(serviceDefinition, abortSignal);
8199
- }
8200
- else {
8201
- return await this.addServicesAsync(serviceDefinition);
8202
- }
8191
+ addService(serviceDefinition) {
8192
+ return this.addServices(serviceDefinition);
8203
8193
  }
8204
- async _addServiceAsync(service, abortSignal) {
8194
+ _addService(service) {
8205
8195
  if (this._isDisposed) {
8206
8196
  throw new Error(`'${this._friendlyName}' container is disposed.`);
8207
8197
  }
@@ -8214,7 +8204,7 @@ class ServiceContainer {
8214
8204
  this._serviceDefinitions.set(contract, service);
8215
8205
  });
8216
8206
  const dependencies = service.consumes?.map((contract) => this._resolveDependency(contract, service)) ?? [];
8217
- this._serviceInstances.set(service, await service.factory(...dependencies, abortSignal));
8207
+ this._serviceInstances.set(service, service.factory(...dependencies));
8218
8208
  }
8219
8209
  /**
8220
8210
  * Resolves a dependency by contract identity for a consuming service.
@@ -8227,15 +8217,15 @@ class ServiceContainer {
8227
8217
  _resolveDependency(contract, consumer) {
8228
8218
  const definition = this._serviceDefinitions.get(contract);
8229
8219
  if (definition) {
8220
+ const instance = this._serviceInstances.get(definition);
8221
+ if (!instance) {
8222
+ throw new Error(`Service '${contract.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);
8223
+ }
8230
8224
  let dependentDefinitions = this._serviceDependents.get(definition);
8231
8225
  if (!dependentDefinitions) {
8232
8226
  this._serviceDependents.set(definition, (dependentDefinitions = new Set()));
8233
8227
  }
8234
8228
  dependentDefinitions.add(consumer);
8235
- const instance = this._serviceInstances.get(definition);
8236
- if (!instance) {
8237
- throw new Error(`Service '${contract.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);
8238
- }
8239
8229
  return instance;
8240
8230
  }
8241
8231
  if (this._parent) {
@@ -8389,6 +8379,8 @@ function MakeModularTool(options) {
8389
8379
  if (themeMode) {
8390
8380
  settingsStore.writeSetting(ThemeModeSettingDescriptor, themeMode);
8391
8381
  }
8382
+ // This deferred resolves once the React effect cleanup (which disposes the ServiceContainer) is complete.
8383
+ const disposeDeferred = new Deferred();
8392
8384
  const modularToolRootComponent = () => {
8393
8385
  const classes = useStyles$H();
8394
8386
  const [extensionManagerContext, setExtensionManagerContext] = useState();
@@ -8421,13 +8413,13 @@ function MakeModularTool(options) {
8421
8413
  const initializeExtensionManagerAsync = async () => {
8422
8414
  const serviceContainer = new ServiceContainer("ModularToolContainer", parentContainer);
8423
8415
  // Expose the settings store as a service so other services can read/write settings.
8424
- await serviceContainer.addServiceAsync({
8416
+ serviceContainer.addService({
8425
8417
  friendlyName: "Settings Store",
8426
8418
  produces: [SettingsStoreIdentity],
8427
8419
  factory: () => settingsStore,
8428
8420
  });
8429
8421
  // Expose the react context service so other services can add React context providers.
8430
- await serviceContainer.addServiceAsync({
8422
+ serviceContainer.addService({
8431
8423
  friendlyName: "React Context Service",
8432
8424
  produces: [ReactContextServiceIdentity],
8433
8425
  factory: () => ({
@@ -8446,7 +8438,7 @@ function MakeModularTool(options) {
8446
8438
  }),
8447
8439
  });
8448
8440
  // Expose the toast service so non-React code (e.g. Observable callbacks) can show toasts.
8449
- await serviceContainer.addServiceAsync({
8441
+ serviceContainer.addService({
8450
8442
  friendlyName: "Toast Service",
8451
8443
  produces: [ToastServiceIdentity],
8452
8444
  factory: () => ({
@@ -8456,9 +8448,9 @@ function MakeModularTool(options) {
8456
8448
  }),
8457
8449
  });
8458
8450
  // Register the shell service (top level toolbar/side pane UI layout).
8459
- await serviceContainer.addServiceAsync(MakeShellServiceDefinition(options));
8451
+ serviceContainer.addService(MakeShellServiceDefinition(options));
8460
8452
  // Register a service that simply consumes the services we need before first render.
8461
- await serviceContainer.addServiceAsync({
8453
+ serviceContainer.addService({
8462
8454
  friendlyName: "Service Bootstrapper",
8463
8455
  consumes: [RootComponentServiceIdentity],
8464
8456
  factory: (rootComponent) => {
@@ -8470,18 +8462,18 @@ function MakeModularTool(options) {
8470
8462
  },
8471
8463
  });
8472
8464
  // Register the theme service (exposes the current theme to other services).
8473
- await serviceContainer.addServiceAsync(ThemeServiceDefinition);
8465
+ serviceContainer.addService(ThemeServiceDefinition);
8474
8466
  // Register the theme selector service (for selecting the theme) if theming is configured.
8475
8467
  if (showThemeSelector) {
8476
- await serviceContainer.addServiceAsync(ThemeSelectorServiceDefinition);
8468
+ serviceContainer.addService(ThemeSelectorServiceDefinition);
8477
8469
  }
8478
8470
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8479
8471
  if (extensionFeeds.length > 0) {
8480
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-DPHWDyzb.js');
8481
- await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8472
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-DA8_rCpk.js');
8473
+ serviceContainer.addService(ExtensionListServiceDefinition);
8482
8474
  }
8483
8475
  // Register all external services (that make up a unique tool).
8484
- await serviceContainer.addServicesAsync(...serviceDefinitions);
8476
+ serviceContainer.addServices(...serviceDefinitions);
8485
8477
  // Create the extension manager, passing along the registry for runtime changes to the registered services.
8486
8478
  const extensionManager = await ExtensionManager.CreateAsync(namespace, serviceContainer, extensionFeeds, setExtensionInstallError);
8487
8479
  // Check query params for required extensions. This lets users share links with sets of extensions.
@@ -8529,7 +8521,8 @@ function MakeModularTool(options) {
8529
8521
  // eslint-disable-next-line github/no-then
8530
8522
  .catch((error) => {
8531
8523
  Logger.Error(`Failed to dispose of the modular tool: ${error}`);
8532
- });
8524
+ })
8525
+ .finally(() => disposeDeferred.resolve());
8533
8526
  };
8534
8527
  }, []);
8535
8528
  const onAcceptRequiredExtensions = useCallback(() => {
@@ -8561,6 +8554,7 @@ function MakeModularTool(options) {
8561
8554
  reactRoot.render(createElement(modularToolRootComponent));
8562
8555
  let disposed = false;
8563
8556
  return {
8557
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
8564
8558
  dispose: () => {
8565
8559
  // Unmount and restore the original container element display.
8566
8560
  if (!disposed) {
@@ -8568,6 +8562,9 @@ function MakeModularTool(options) {
8568
8562
  reactRoot.unmount();
8569
8563
  containerElement.style.display = originalContainerElementDisplay;
8570
8564
  }
8565
+ // The promise resolves once the React effect cleanup
8566
+ // (which disposes the ServiceContainer) has completed.
8567
+ return disposeDeferred.promise;
8571
8568
  },
8572
8569
  };
8573
8570
  }
@@ -8593,21 +8590,270 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
8593
8590
  description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
8594
8591
  keywords: ["creation", "tools"],
8595
8592
  ...BabylonWebResources,
8596
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-DJ1GVvH-.js'),
8593
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-_L22btR5.js'),
8597
8594
  },
8598
8595
  {
8599
8596
  name: "Reflector",
8600
8597
  description: "Connects to the Reflector Bridge for real-time scene synchronization with the Babylon.js Sandbox.",
8601
8598
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
8602
8599
  ...BabylonWebResources,
8603
- getExtensionModuleAsync: async () => await import('./reflectorService-Cd41l6Tr.js'),
8600
+ getExtensionModuleAsync: async () => await import('./reflectorService-BMLxtZT7.js'),
8604
8601
  },
8605
8602
  ]);
8606
8603
 
8607
8604
  /**
8608
- * The service identity for the inspectable command registry.
8605
+ * The service identity for the CLI connection status.
8606
+ * @experimental
8607
+ * @internal
8608
+ */
8609
+ const CliConnectionStatusIdentity = Symbol("CliConnectionStatus");
8610
+
8611
+ /**
8612
+ * The service identity for the bridge command registry.
8613
+ * @experimental
8614
+ * @internal
8609
8615
  */
8610
- const InspectableCommandRegistryIdentity = Symbol("InspectableCommandRegistry");
8616
+ const BridgeCommandRegistryIdentity = Symbol("BridgeCommandRegistry");
8617
+
8618
+ /**
8619
+ * Creates the service definition for the CLI Bridge Service.
8620
+ * @param options The options for connecting to the bridge.
8621
+ * @returns A service definition that produces an IBridgeCommandRegistry and ICliConnectionStatus.
8622
+ * @experimental
8623
+ * @internal
8624
+ */
8625
+ function MakeBridgeServiceDefinition(options) {
8626
+ return {
8627
+ friendlyName: "CLI Bridge Service",
8628
+ produces: [BridgeCommandRegistryIdentity, CliConnectionStatusIdentity],
8629
+ factory: () => {
8630
+ const commands = new Map();
8631
+ let ws = null;
8632
+ let reconnectTimer = null;
8633
+ let disposed = false;
8634
+ let enabled = options.autoStart;
8635
+ let connected = false;
8636
+ const onConnectionStatusChanged = new Observable();
8637
+ function notifyStatusChanged() {
8638
+ onConnectionStatusChanged.notifyObservers();
8639
+ }
8640
+ function setConnected(value) {
8641
+ if (connected !== value) {
8642
+ connected = value;
8643
+ notifyStatusChanged();
8644
+ }
8645
+ }
8646
+ function sendToBridge(message) {
8647
+ ws?.send(JSON.stringify(message));
8648
+ }
8649
+ function connect() {
8650
+ if (disposed || !enabled) {
8651
+ return;
8652
+ }
8653
+ // Close any existing WebSocket to avoid orphaned connections that
8654
+ // keep a stale session alive on the bridge.
8655
+ if (ws) {
8656
+ const oldWs = ws;
8657
+ oldWs.onopen = null;
8658
+ oldWs.onclose = null;
8659
+ oldWs.onmessage = null;
8660
+ oldWs.onerror = null;
8661
+ oldWs.close();
8662
+ ws = null;
8663
+ }
8664
+ try {
8665
+ // NOTE: The browser unconditionally logs a console error for failed WebSocket
8666
+ // connections at the network level. This cannot be suppressed from JavaScript.
8667
+ ws = new WebSocket(`ws://127.0.0.1:${options.port}`);
8668
+ }
8669
+ catch {
8670
+ ws = null;
8671
+ setConnected(false);
8672
+ Logger.Warn(`CLIBridgeService: Failed to create WebSocket connection on port ${options.port}.`);
8673
+ scheduleReconnect();
8674
+ return;
8675
+ }
8676
+ ws.onopen = () => {
8677
+ setConnected(true);
8678
+ sendToBridge({ type: "register", name: options.name });
8679
+ };
8680
+ ws.onmessage = (event) => {
8681
+ try {
8682
+ const message = JSON.parse(event.data);
8683
+ void handleMessage(message);
8684
+ }
8685
+ catch {
8686
+ Logger.Warn("CLIBridgeService: Failed to parse message from bridge.");
8687
+ }
8688
+ };
8689
+ ws.onclose = () => {
8690
+ ws = null;
8691
+ setConnected(false);
8692
+ scheduleReconnect();
8693
+ };
8694
+ ws.onerror = () => {
8695
+ // onclose will fire after onerror, which handles reconnection.
8696
+ };
8697
+ }
8698
+ function disconnect() {
8699
+ if (reconnectTimer !== null) {
8700
+ clearTimeout(reconnectTimer);
8701
+ reconnectTimer = null;
8702
+ }
8703
+ if (ws) {
8704
+ ws.onclose = null;
8705
+ ws.close();
8706
+ ws = null;
8707
+ }
8708
+ setConnected(false);
8709
+ }
8710
+ function scheduleReconnect() {
8711
+ if (disposed || !enabled || reconnectTimer !== null) {
8712
+ return;
8713
+ }
8714
+ reconnectTimer = setTimeout(() => {
8715
+ reconnectTimer = null;
8716
+ connect();
8717
+ }, 3000);
8718
+ }
8719
+ async function handleMessage(message) {
8720
+ switch (message.type) {
8721
+ case "listCommands": {
8722
+ const commandList = Array.from(commands.values()).map((cmd) => ({
8723
+ id: cmd.id,
8724
+ description: cmd.description,
8725
+ args: cmd.args,
8726
+ }));
8727
+ sendToBridge({
8728
+ type: "commandListResponse",
8729
+ requestId: message.requestId,
8730
+ commands: commandList,
8731
+ });
8732
+ break;
8733
+ }
8734
+ case "getInfo": {
8735
+ sendToBridge({
8736
+ type: "infoResponse",
8737
+ requestId: message.requestId,
8738
+ name: options.name,
8739
+ });
8740
+ break;
8741
+ }
8742
+ case "execCommand": {
8743
+ const command = commands.get(message.commandId);
8744
+ if (!command) {
8745
+ sendToBridge({
8746
+ type: "commandResponse",
8747
+ requestId: message.requestId,
8748
+ error: `Unknown command: ${message.commandId}`,
8749
+ });
8750
+ break;
8751
+ }
8752
+ try {
8753
+ const result = await command.executeAsync(message.args);
8754
+ sendToBridge({
8755
+ type: "commandResponse",
8756
+ requestId: message.requestId,
8757
+ result,
8758
+ });
8759
+ }
8760
+ catch (error) {
8761
+ sendToBridge({
8762
+ type: "commandResponse",
8763
+ requestId: message.requestId,
8764
+ error: String(error),
8765
+ });
8766
+ }
8767
+ break;
8768
+ }
8769
+ }
8770
+ }
8771
+ if (enabled) {
8772
+ connect();
8773
+ }
8774
+ const registry = {
8775
+ addCommand(descriptor) {
8776
+ if (commands.has(descriptor.id)) {
8777
+ throw new Error(`Command '${descriptor.id}' is already registered.`);
8778
+ }
8779
+ commands.set(descriptor.id, descriptor);
8780
+ return {
8781
+ dispose: () => {
8782
+ commands.delete(descriptor.id);
8783
+ },
8784
+ };
8785
+ },
8786
+ get isEnabled() {
8787
+ return enabled;
8788
+ },
8789
+ set isEnabled(value) {
8790
+ if (enabled !== value) {
8791
+ enabled = value;
8792
+ if (enabled) {
8793
+ connect();
8794
+ }
8795
+ else {
8796
+ disconnect();
8797
+ }
8798
+ notifyStatusChanged();
8799
+ }
8800
+ },
8801
+ get isConnected() {
8802
+ return connected;
8803
+ },
8804
+ onConnectionStatusChanged,
8805
+ dispose: () => {
8806
+ disposed = true;
8807
+ enabled = false;
8808
+ disconnect();
8809
+ commands.clear();
8810
+ onConnectionStatusChanged.clear();
8811
+ },
8812
+ };
8813
+ return registry;
8814
+ },
8815
+ };
8816
+ }
8817
+
8818
+ const DefaultPort$1 = 4400;
8819
+ /**
8820
+ * Creates a headless {@link ServiceContainer} that hosts a bridge service.
8821
+ *
8822
+ * The returned token owns the container. Dispose it to tear down the bridge.
8823
+ *
8824
+ * @param options Optional configuration for the bridge.
8825
+ * @returns A {@link ModularBridgeToken} that owns the container.
8826
+ * @experimental
8827
+ * @internal
8828
+ */
8829
+ function MakeModularBridge(options) {
8830
+ const serviceContainer = new ServiceContainer("ModularBridgeContainer");
8831
+ const bridgeDefinition = MakeBridgeServiceDefinition({
8832
+ port: options?.port ?? DefaultPort$1,
8833
+ get name() {
8834
+ return options?.name ?? (typeof document !== "undefined" ? document.title : "Babylon.js Scene");
8835
+ },
8836
+ autoStart: options?.autoStart ?? false,
8837
+ });
8838
+ const allDefinitions = [bridgeDefinition];
8839
+ if (options?.serviceDefinitions) {
8840
+ allDefinitions.push(...options.serviceDefinitions);
8841
+ }
8842
+ serviceContainer.addServices(...allDefinitions);
8843
+ let disposed = false;
8844
+ return {
8845
+ get serviceContainer() {
8846
+ return serviceContainer;
8847
+ },
8848
+ get isDisposed() {
8849
+ return disposed;
8850
+ },
8851
+ dispose() {
8852
+ disposed = true;
8853
+ serviceContainer.dispose();
8854
+ },
8855
+ };
8856
+ }
8611
8857
 
8612
8858
  const UniqueIdArg = {
8613
8859
  name: "uniqueId",
@@ -8681,7 +8927,7 @@ function MakeQueryCommand(collection, sceneContext) {
8681
8927
  */
8682
8928
  const EntityQueryServiceDefinition = {
8683
8929
  friendlyName: "Entity Query Service",
8684
- consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
8930
+ consumes: [BridgeCommandRegistryIdentity, SceneContextIdentity],
8685
8931
  factory: (commandRegistry, sceneContext) => {
8686
8932
  const collections = [
8687
8933
  {
@@ -8846,205 +9092,13 @@ const EntityQueryServiceDefinition = {
8846
9092
  },
8847
9093
  };
8848
9094
 
8849
- /**
8850
- * The service identity for the CLI connection status.
8851
- */
8852
- const CliConnectionStatusIdentity = Symbol("CliConnectionStatus");
8853
-
8854
- /**
8855
- * Creates the service definition for the InspectableBridgeService.
8856
- * @param options The options for connecting to the bridge.
8857
- * @returns A service definition that produces an IInspectableCommandRegistry.
8858
- */
8859
- function MakeInspectableBridgeServiceDefinition(options) {
8860
- return {
8861
- friendlyName: "Inspectable Bridge Service",
8862
- produces: [InspectableCommandRegistryIdentity, CliConnectionStatusIdentity],
8863
- factory: () => {
8864
- const commands = new Map();
8865
- let ws = null;
8866
- let reconnectTimer = null;
8867
- let disposed = false;
8868
- let enabled = options.autoStart;
8869
- let connected = false;
8870
- const onConnectionStatusChanged = new Observable();
8871
- function notifyStatusChanged() {
8872
- onConnectionStatusChanged.notifyObservers();
8873
- }
8874
- function setConnected(value) {
8875
- if (connected !== value) {
8876
- connected = value;
8877
- notifyStatusChanged();
8878
- }
8879
- }
8880
- function sendToBridge(message) {
8881
- ws?.send(JSON.stringify(message));
8882
- }
8883
- function connect() {
8884
- if (disposed || !enabled) {
8885
- return;
8886
- }
8887
- try {
8888
- // NOTE: The browser unconditionally logs a console error for failed WebSocket
8889
- // connections at the network level. This cannot be suppressed from JavaScript.
8890
- ws = new WebSocket(`ws://127.0.0.1:${options.port}`);
8891
- }
8892
- catch {
8893
- ws = null;
8894
- setConnected(false);
8895
- Logger.Warn(`InspectableBridgeService: Failed to create WebSocket connection on port ${options.port}.`);
8896
- scheduleReconnect();
8897
- return;
8898
- }
8899
- ws.onopen = () => {
8900
- setConnected(true);
8901
- sendToBridge({ type: "register", name: options.name });
8902
- };
8903
- ws.onmessage = (event) => {
8904
- try {
8905
- const message = JSON.parse(event.data);
8906
- void handleMessage(message);
8907
- }
8908
- catch {
8909
- Logger.Warn("InspectableBridgeService: Failed to parse message from bridge.");
8910
- }
8911
- };
8912
- ws.onclose = () => {
8913
- ws = null;
8914
- setConnected(false);
8915
- scheduleReconnect();
8916
- };
8917
- ws.onerror = () => {
8918
- // onclose will fire after onerror, which handles reconnection.
8919
- };
8920
- }
8921
- function disconnect() {
8922
- if (reconnectTimer !== null) {
8923
- clearTimeout(reconnectTimer);
8924
- reconnectTimer = null;
8925
- }
8926
- if (ws) {
8927
- ws.onclose = null;
8928
- ws.close();
8929
- ws = null;
8930
- }
8931
- setConnected(false);
8932
- }
8933
- function scheduleReconnect() {
8934
- if (disposed || !enabled || reconnectTimer !== null) {
8935
- return;
8936
- }
8937
- reconnectTimer = setTimeout(() => {
8938
- reconnectTimer = null;
8939
- connect();
8940
- }, 3000);
8941
- }
8942
- async function handleMessage(message) {
8943
- switch (message.type) {
8944
- case "listCommands": {
8945
- const commandList = Array.from(commands.values()).map((cmd) => ({
8946
- id: cmd.id,
8947
- description: cmd.description,
8948
- args: cmd.args,
8949
- }));
8950
- sendToBridge({
8951
- type: "commandListResponse",
8952
- requestId: message.requestId,
8953
- commands: commandList,
8954
- });
8955
- break;
8956
- }
8957
- case "getInfo": {
8958
- sendToBridge({
8959
- type: "infoResponse",
8960
- requestId: message.requestId,
8961
- name: options.name,
8962
- });
8963
- break;
8964
- }
8965
- case "execCommand": {
8966
- const command = commands.get(message.commandId);
8967
- if (!command) {
8968
- sendToBridge({
8969
- type: "commandResponse",
8970
- requestId: message.requestId,
8971
- error: `Unknown command: ${message.commandId}`,
8972
- });
8973
- break;
8974
- }
8975
- try {
8976
- const result = await command.executeAsync(message.args);
8977
- sendToBridge({
8978
- type: "commandResponse",
8979
- requestId: message.requestId,
8980
- result,
8981
- });
8982
- }
8983
- catch (error) {
8984
- sendToBridge({
8985
- type: "commandResponse",
8986
- requestId: message.requestId,
8987
- error: String(error),
8988
- });
8989
- }
8990
- break;
8991
- }
8992
- }
8993
- }
8994
- if (enabled) {
8995
- connect();
8996
- }
8997
- const registry = {
8998
- addCommand(descriptor) {
8999
- if (commands.has(descriptor.id)) {
9000
- throw new Error(`Command '${descriptor.id}' is already registered.`);
9001
- }
9002
- commands.set(descriptor.id, descriptor);
9003
- return {
9004
- dispose: () => {
9005
- commands.delete(descriptor.id);
9006
- },
9007
- };
9008
- },
9009
- get isEnabled() {
9010
- return enabled;
9011
- },
9012
- set isEnabled(value) {
9013
- if (enabled !== value) {
9014
- enabled = value;
9015
- if (enabled) {
9016
- connect();
9017
- }
9018
- else {
9019
- disconnect();
9020
- }
9021
- notifyStatusChanged();
9022
- }
9023
- },
9024
- get isConnected() {
9025
- return connected;
9026
- },
9027
- onConnectionStatusChanged,
9028
- dispose: () => {
9029
- disposed = true;
9030
- enabled = false;
9031
- disconnect();
9032
- commands.clear();
9033
- onConnectionStatusChanged.clear();
9034
- },
9035
- };
9036
- return registry;
9037
- },
9038
- };
9039
- }
9040
-
9041
9095
  /**
9042
9096
  * Service that registers CLI commands for performance tracing using the PerformanceViewerCollector.
9043
9097
  * start-perf-trace begins collecting data, stop-perf-trace stops and returns the collected data as JSON.
9044
9098
  */
9045
9099
  const PerfTraceCommandServiceDefinition = {
9046
9100
  friendlyName: "Perf Trace Command Service",
9047
- consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
9101
+ consumes: [BridgeCommandRegistryIdentity, SceneContextIdentity],
9048
9102
  factory: (commandRegistry, sceneContext) => {
9049
9103
  let perfCollector;
9050
9104
  const startReg = commandRegistry.addCommand({
@@ -9111,7 +9165,7 @@ const PerfTraceCommandServiceDefinition = {
9111
9165
  */
9112
9166
  const ScreenshotCommandServiceDefinition = {
9113
9167
  friendlyName: "Screenshot Command Service",
9114
- consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
9168
+ consumes: [BridgeCommandRegistryIdentity, SceneContextIdentity],
9115
9169
  factory: (commandRegistry, sceneContext) => {
9116
9170
  const registration = commandRegistry.addCommand({
9117
9171
  id: "take-screenshot",
@@ -9201,7 +9255,7 @@ const ScreenshotCommandServiceDefinition = {
9201
9255
  */
9202
9256
  const ShaderCommandServiceDefinition = {
9203
9257
  friendlyName: "Shader Command Service",
9204
- consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
9258
+ consumes: [BridgeCommandRegistryIdentity, SceneContextIdentity],
9205
9259
  factory: (commandRegistry, sceneContext) => {
9206
9260
  const registration = commandRegistry.addCommand({
9207
9261
  id: "get-shader-code",
@@ -9273,7 +9327,7 @@ const ShaderCommandServiceDefinition = {
9273
9327
  */
9274
9328
  const StatsCommandServiceDefinition = {
9275
9329
  friendlyName: "Stats Command Service",
9276
- consumes: [InspectableCommandRegistryIdentity, SceneContextIdentity],
9330
+ consumes: [BridgeCommandRegistryIdentity, SceneContextIdentity],
9277
9331
  factory: (commandRegistry, sceneContext) => {
9278
9332
  let sceneInstrumentation;
9279
9333
  let engineInstrumentation;
@@ -9439,15 +9493,32 @@ const InspectableStates = new Map();
9439
9493
  function _StartInspectable(scene, options) {
9440
9494
  let state = InspectableStates.get(scene);
9441
9495
  if (!state) {
9442
- const serviceContainer = new ServiceContainer("InspectableContainer");
9496
+ const disposeActions = [];
9497
+ // When a bridgeToken is provided, use its container as the parent and skip bridge creation.
9498
+ // When not, create an internal bridge container via MakeModularBridge.
9499
+ let bridgeToken;
9500
+ if (options?.bridgeToken) {
9501
+ bridgeToken = options.bridgeToken;
9502
+ }
9503
+ else {
9504
+ bridgeToken = MakeModularBridge({
9505
+ port: options?.port ?? DefaultPort,
9506
+ name: options?.name,
9507
+ autoStart: options?.autoStart,
9508
+ });
9509
+ disposeActions.push(() => bridgeToken.dispose());
9510
+ }
9511
+ const serviceContainer = new ServiceContainer("InspectableContainer", bridgeToken.serviceContainer);
9512
+ disposeActions.push(() => serviceContainer.dispose());
9443
9513
  let fullyDisposed = false;
9444
9514
  const fullyDispose = () => {
9445
9515
  InspectableStates.delete(scene);
9446
9516
  fullyDisposed = true;
9447
- serviceContainer.dispose();
9448
- sceneDisposeObserver.remove();
9517
+ for (const action of disposeActions.reverse()) {
9518
+ action();
9519
+ }
9449
9520
  };
9450
- // Initialize the service container asynchronously.
9521
+ // Initialize the service container.
9451
9522
  const sceneContextServiceDefinition = {
9452
9523
  friendlyName: "Inspectable Scene Context",
9453
9524
  produces: [SceneContextIdentity],
@@ -9456,46 +9527,25 @@ function _StartInspectable(scene, options) {
9456
9527
  currentSceneObservable: new Observable(),
9457
9528
  }),
9458
9529
  };
9459
- const readyPromise = (async () => {
9460
- await serviceContainer.addServicesAsync(sceneContextServiceDefinition, MakeInspectableBridgeServiceDefinition({
9461
- port: options?.port ?? DefaultPort,
9462
- get name() {
9463
- return options?.name ?? (typeof document !== "undefined" ? document.title : "Babylon.js Scene");
9464
- },
9465
- autoStart: options?.autoStart ?? false,
9466
- }), EntityQueryServiceDefinition, ScreenshotCommandServiceDefinition, ShaderCommandServiceDefinition, StatsCommandServiceDefinition, PerfTraceCommandServiceDefinition);
9467
- })();
9530
+ serviceContainer.addServices(sceneContextServiceDefinition, EntityQueryServiceDefinition, ScreenshotCommandServiceDefinition, ShaderCommandServiceDefinition, StatsCommandServiceDefinition, PerfTraceCommandServiceDefinition);
9468
9531
  state = {
9469
9532
  refCount: 0,
9470
9533
  get fullyDisposed() {
9471
9534
  return fullyDisposed;
9472
9535
  },
9473
9536
  serviceContainer,
9474
- sceneDisposeObserver: { remove: () => { } },
9475
9537
  fullyDispose,
9476
- readyPromise,
9477
9538
  };
9478
9539
  const capturedState = state;
9479
9540
  InspectableStates.set(scene, state);
9480
- // Auto-dispose when the scene is disposed.
9481
- const sceneDisposeObserver = scene.onDisposeObservable.addOnce(() => {
9541
+ // Auto-dispose when the scene is disposed. Use insertFirst so that
9542
+ // callbacks registered later (e.g. ShowInspector) fire before this one,
9543
+ // ensuring child containers are disposed before this parent container.
9544
+ const sceneDisposeObserver = scene.onDisposeObservable.add(() => {
9482
9545
  capturedState.refCount = 0;
9483
9546
  capturedState.fullyDispose();
9484
- });
9485
- state.sceneDisposeObserver = sceneDisposeObserver;
9486
- // Handle initialization failure (guard against already-disposed state).
9487
- void (async () => {
9488
- try {
9489
- await readyPromise;
9490
- }
9491
- catch (error) {
9492
- if (InspectableStates.has(scene)) {
9493
- Logger.Error(`Failed to initialize Inspectable: ${error}`);
9494
- capturedState.refCount = 0;
9495
- capturedState.fullyDispose();
9496
- }
9497
- }
9498
- })();
9547
+ }, undefined, true, undefined, true);
9548
+ disposeActions.push(() => sceneDisposeObserver.remove());
9499
9549
  }
9500
9550
  state.refCount++;
9501
9551
  const { serviceContainer } = state;
@@ -9503,21 +9553,9 @@ function _StartInspectable(scene, options) {
9503
9553
  // If additional service definitions were provided, add them in a separate call
9504
9554
  // so they can be independently removed when this token is disposed.
9505
9555
  let extraServicesDisposable;
9506
- const extraAbortController = new AbortController();
9507
9556
  const extraServiceDefinitions = options?.serviceDefinitions;
9508
9557
  if (extraServiceDefinitions && extraServiceDefinitions.length > 0) {
9509
- // Wait for the built-in services to be ready, then add the extra ones.
9510
- void (async () => {
9511
- try {
9512
- await owningState.readyPromise;
9513
- extraServicesDisposable = await serviceContainer.addServicesAsync(...extraServiceDefinitions, extraAbortController.signal);
9514
- }
9515
- catch (error) {
9516
- if (!extraAbortController.signal.aborted) {
9517
- Logger.Error(`Failed to add extra inspectable services: ${error}`);
9518
- }
9519
- }
9520
- })();
9558
+ extraServicesDisposable = serviceContainer.addServices(...extraServiceDefinitions);
9521
9559
  }
9522
9560
  let disposed = false;
9523
9561
  const token = {
@@ -9532,8 +9570,7 @@ function _StartInspectable(scene, options) {
9532
9570
  return;
9533
9571
  }
9534
9572
  disposed = true;
9535
- // Abort any in-flight extra service initialization and remove already-added extra services.
9536
- extraAbortController.abort();
9573
+ // Remove extra services that were added for this token.
9537
9574
  extraServicesDisposable?.dispose();
9538
9575
  owningState.refCount--;
9539
9576
  if (owningState.refCount <= 0) {
@@ -9545,9 +9582,12 @@ function _StartInspectable(scene, options) {
9545
9582
  }
9546
9583
  /**
9547
9584
  * Makes a scene inspectable by connecting it to the Inspector CLI bridge.
9548
- * This creates a headless {@link ServiceContainer} (no UI) and registers the
9549
- * {@link InspectableBridgeService} which opens a WebSocket to the bridge and
9550
- * exposes a command registry for CLI-invocable commands.
9585
+ * This creates a headless {@link ServiceContainer} (no UI) that registers
9586
+ * scene-specific CLI command services (entity query, screenshot, shader, stats, etc.).
9587
+ *
9588
+ * When {@link InspectableOptions.bridgeToken} is provided, the inspectable container
9589
+ * is created as a child of the CLI container, inheriting the bridge and command registry.
9590
+ * When not provided, a bridge container is created internally via {@link MakeModularBridge}.
9551
9591
  *
9552
9592
  * Multiple callers may call this for the same scene. Each returned token is
9553
9593
  * ref-counted — the underlying connection is only torn down when all tokens
@@ -23225,7 +23265,7 @@ const InspectorLock = new AsyncLock();
23225
23265
  */
23226
23266
  function ShowInspector(scene, options = {}) {
23227
23267
  // Dispose of any existing inspector for this scene.
23228
- InspectorTokens.get(scene)?.dispose();
23268
+ void InspectorTokens.get(scene)?.dispose();
23229
23269
  // Default the dispose logic to a no-op until we know that we are actually going
23230
23270
  // to show the Inspector and there will be cleanup work to do.
23231
23271
  let disposeAsync = async () => await Promise.resolve();
@@ -23234,9 +23274,9 @@ function ShowInspector(scene, options = {}) {
23234
23274
  let isDisposed = false;
23235
23275
  const onDisposed = new Observable();
23236
23276
  const inspectorToken = {
23237
- dispose() {
23238
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
23239
- InspectorLock.lockAsync(async () => {
23277
+ // eslint-disable-next-line @typescript-eslint/naming-convention
23278
+ async dispose() {
23279
+ await InspectorLock.lockAsync(async () => {
23240
23280
  await disposeAsync();
23241
23281
  isDisposed = true;
23242
23282
  onDisposed.notifyObservers();
@@ -23258,9 +23298,9 @@ function ShowInspector(scene, options = {}) {
23258
23298
  layoutMode: "overlay",
23259
23299
  ...options,
23260
23300
  };
23261
- // Sequentialize showing the inspector (e.g. don't start showing until after a previous hide (for example) is finished).
23301
+ // Sequentialize showing the inspector (e.g. don't start showing until after a previous hide is finished).
23262
23302
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
23263
- InspectorLock.lockAsync(async () => {
23303
+ InspectorLock.lockAsync(() => {
23264
23304
  let parentElement = options.containerElement ?? null;
23265
23305
  // If a container element was not found, find an appropriate one above the engine's rendering canvas.
23266
23306
  if (!parentElement) {
@@ -23320,7 +23360,7 @@ function ShowInspector(scene, options = {}) {
23320
23360
  const serviceDefinitions = [];
23321
23361
  // Ensure the inspectable bridge is running for this scene. The inspector's
23322
23362
  // ServiceContainer will use the inspectable container as a parent, inheriting
23323
- // services like ISceneContext and IInspectableCommandRegistry.
23363
+ // services like ISceneContext and IBridgeCommandRegistry.
23324
23364
  const inspectableToken = _StartInspectable(scene);
23325
23365
  disposeActions.push(() => inspectableToken.dispose());
23326
23366
  // Create a container element for the inspector UI.
@@ -23431,10 +23471,12 @@ function ShowInspector(scene, options = {}) {
23431
23471
  leftPaneDefaultCollapsed: options.leftPaneDefaultCollapsed,
23432
23472
  rightPaneDefaultCollapsed: options.rightPaneDefaultCollapsed,
23433
23473
  });
23434
- disposeActions.push(() => modularTool.dispose());
23435
- const sceneDisposedObserver = scene.onDisposeObservable.addOnce(() => {
23436
- inspectorToken.dispose();
23437
- });
23474
+ disposeActions.push(async () => await modularTool.dispose());
23475
+ // Use insertFirst so this fires before StartInspectable's scene-dispose
23476
+ // callback, ensuring the UI child container is torn down first.
23477
+ const sceneDisposedObserver = scene.onDisposeObservable.add(() => {
23478
+ void inspectorToken.dispose();
23479
+ }, undefined, true, undefined, true);
23438
23480
  disposeActions.push(() => sceneDisposedObserver.remove());
23439
23481
  disposeActions.push(() => {
23440
23482
  InspectorTokens.delete(scene);
@@ -23920,7 +23962,7 @@ class Inspector {
23920
23962
  this._CurrentInstance.disposeToken.onDisposed.addOnce(() => (this._CurrentInstance = null));
23921
23963
  }
23922
23964
  static Hide() {
23923
- this._CurrentInstance?.disposeToken.dispose();
23965
+ void this._CurrentInstance?.disposeToken.dispose();
23924
23966
  }
23925
23967
  // @ts-expect-error TS6133: This is private, but used by debugLayer (same as Inspector v1).
23926
23968
  static _SetNewScene(scene) {
@@ -24251,5 +24293,5 @@ const TextAreaPropertyLine = (props) => {
24251
24293
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
24252
24294
  AttachDebugLayer();
24253
24295
 
24254
- export { GizmoServiceIdentity as $, Accordion as A, Button as B, CheckboxPropertyLine as C, ColorPickerPopup as D, ColorStepGradientComponent as E, ComboBox as F, ComboBoxPropertyLine as G, ConstructorFactory as H, ConvertOptions as I, DebugServiceIdentity as J, DetachDebugLayer as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DraggableLine as O, Popover as P, Dropdown as Q, EntitySelector as R, ShellServiceIdentity as S, TextInputPropertyLine as T, ErrorBoundary as U, Vector3PropertyLine as V, ExtensibleAccordion as W, FactorGradientComponent as X, FactorGradientList as Y, FileUploadLine as Z, GetPropertyDescriptor as _, useToast as a, ToastProvider as a$, HexPropertyLine as a0, InfoLabel as a1, InputHexField as a2, InputHsvField as a3, InspectableCommandRegistryIdentity as a4, Inspector as a5, InterceptFunction as a6, InterceptProperty as a7, IsPropertyReadonly as a8, LineContainer as a9, SelectionServiceDefinition as aA, SettingsServiceIdentity as aB, SettingsStore as aC, SettingsStoreIdentity as aD, ShowInspector as aE, SidePaneContainer as aF, SkeletonSelector as aG, Slider as aH, SpinButton as aI, StartInspectable as aJ, StatsServiceIdentity as aK, StringDropdown as aL, StringDropdownPropertyLine as aM, StringifiedPropertyLine as aN, Switch as aO, SwitchPropertyLine as aP, SyncedSliderInput as aQ, SyncedSliderPropertyLine as aR, TeachingMoment as aS, TextAreaPropertyLine as aT, TextInput as aU, TextPropertyLine as aV, Textarea as aW, TextureSelector as aX, TextureUpload as aY, Theme as aZ, ThemeServiceIdentity as a_, LinkPropertyLine as aa, LinkToEntityPropertyLine as ab, List as ac, MakeDialogTeachingMoment as ad, MakeLazyComponent as ae, MakeModularTool as af, MakePopoverTeachingMoment as ag, MakePropertyHook as ah, MakeTeachingMoment as ai, MaterialSelector as aj, NodeSelector as ak, NumberDropdown as al, NumberDropdownPropertyLine as am, ObservableCollection as an, Pane as ao, PlaceholderPropertyLine as ap, PositionedPopover as aq, PropertiesServiceIdentity as ar, Property as as, PropertyContext as at, PropertyLine as au, QuaternionPropertyLine as av, RotationVectorPropertyLine as aw, SceneExplorerServiceIdentity as ax, SearchBar as ay, SearchBox as az, useInterceptObservable as b, ToggleButton as b0, Tooltip as b1, UploadButton as b2, Vector2PropertyLine as b3, Vector4PropertyLine as b4, WatcherServiceIdentity as b5, useAngleConverters as b6, useAsyncResource as b7, useColor3Property as b8, useColor4Property as b9, useEventListener as ba, useEventfulState as bb, useKeyListener as bc, useKeyState as bd, useObservableCollection as be, useOrderedObservableCollection as bf, usePollingObservable as bg, usePropertyChangedNotifier as bh, useQuaternionProperty as bi, useResource as bj, useTheme as bk, useThemeMode as bl, useVector3Property as bm, LinkToEntity as c, SpinButtonPropertyLine as d, useProperty as e, SceneContextIdentity as f, SelectionServiceIdentity as g, useObservableState as h, AccordionSection as i, ButtonLine as j, ToolsServiceIdentity as k, AccordionSectionItem as l, AttachDebugLayer as m, BooleanBadgePropertyLine as n, BoundProperty as o, BuiltInsExtensionFeed as p, Checkbox as q, ChildWindow as r, Collapse as s, Color3GradientComponent as t, useExtensionManager as u, Color3GradientList as v, Color3PropertyLine as w, Color4GradientComponent as x, Color4GradientList as y, Color4PropertyLine as z };
24255
- //# sourceMappingURL=index-BaFR1FRV.js.map
24296
+ export { GetPropertyDescriptor as $, Accordion as A, Button as B, CheckboxPropertyLine as C, Color4PropertyLine as D, ColorPickerPopup as E, ColorStepGradientComponent as F, ComboBox as G, ComboBoxPropertyLine as H, ConstructorFactory as I, ConvertOptions as J, DebugServiceIdentity as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DetachDebugLayer as O, Popover as P, DraggableLine as Q, Dropdown as R, ShellServiceIdentity as S, TextInputPropertyLine as T, EntitySelector as U, Vector3PropertyLine as V, ErrorBoundary as W, ExtensibleAccordion as X, FactorGradientComponent as Y, FactorGradientList as Z, FileUploadLine as _, useToast as a, ThemeServiceIdentity as a$, GizmoServiceIdentity as a0, HexPropertyLine as a1, InfoLabel as a2, InputHexField as a3, InputHsvField as a4, Inspector as a5, InterceptFunction as a6, InterceptProperty as a7, IsPropertyReadonly as a8, LineContainer as a9, SearchBox as aA, SelectionServiceDefinition as aB, SettingsServiceIdentity as aC, SettingsStore as aD, SettingsStoreIdentity as aE, ShowInspector as aF, SidePaneContainer as aG, SkeletonSelector as aH, Slider as aI, SpinButton as aJ, StartInspectable as aK, StatsServiceIdentity as aL, StringDropdown as aM, StringDropdownPropertyLine as aN, StringifiedPropertyLine as aO, Switch as aP, SwitchPropertyLine as aQ, SyncedSliderInput as aR, SyncedSliderPropertyLine as aS, TeachingMoment as aT, TextAreaPropertyLine as aU, TextInput as aV, TextPropertyLine as aW, Textarea as aX, TextureSelector as aY, TextureUpload as aZ, Theme as a_, LinkPropertyLine as aa, LinkToEntityPropertyLine as ab, List as ac, MakeDialogTeachingMoment as ad, MakeLazyComponent as ae, MakeModularBridge as af, MakeModularTool as ag, MakePopoverTeachingMoment as ah, MakePropertyHook as ai, MakeTeachingMoment as aj, MaterialSelector as ak, NodeSelector as al, NumberDropdown as am, NumberDropdownPropertyLine as an, ObservableCollection as ao, Pane as ap, PlaceholderPropertyLine as aq, PositionedPopover as ar, PropertiesServiceIdentity as as, Property as at, PropertyContext as au, PropertyLine as av, QuaternionPropertyLine as aw, RotationVectorPropertyLine as ax, SceneExplorerServiceIdentity as ay, SearchBar as az, useInterceptObservable as b, ToastProvider as b0, ToggleButton as b1, Tooltip as b2, UploadButton as b3, Vector2PropertyLine as b4, Vector4PropertyLine as b5, WatcherServiceIdentity as b6, useAngleConverters as b7, useAsyncResource as b8, useColor3Property as b9, useColor4Property as ba, useEventListener as bb, useEventfulState as bc, useKeyListener as bd, useKeyState as be, useObservableCollection as bf, useOrderedObservableCollection as bg, usePollingObservable as bh, usePropertyChangedNotifier as bi, useQuaternionProperty as bj, useResource as bk, useTheme as bl, useThemeMode as bm, useVector3Property as bn, LinkToEntity as c, SpinButtonPropertyLine as d, useProperty as e, SceneContextIdentity as f, SelectionServiceIdentity as g, useObservableState as h, AccordionSection as i, ButtonLine as j, ToolsServiceIdentity as k, AccordionSectionItem as l, AttachDebugLayer as m, BooleanBadgePropertyLine as n, BoundProperty as o, BridgeCommandRegistryIdentity as p, BuiltInsExtensionFeed as q, Checkbox as r, ChildWindow as s, Collapse as t, useExtensionManager as u, Color3GradientComponent as v, Color3GradientList as w, Color3PropertyLine as x, Color4GradientComponent as y, Color4GradientList as z };
24297
+ //# sourceMappingURL=index-nEGqYbP2.js.map