Vue
Provider
Provide (useVircadia)
First setup your main.ts
file to use the Vircadia World SDK using the useVircadia
provider composable. Here is an example:
Example: /client/web_babylon_js/src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import "./assets/main.css";
import {
useVircadia,
DEFAULT_VIRCADIA_INSTANCE_KEY,
Communication,
} from "@vircadia/world-sdk/browser/vue";
import { clientBrowserConfiguration } from "./vircadia.browser.config";
// Initialize Vircadia before creating the app
const vircadiaWorld = useVircadia({
config: {
serverUrl:
clientBrowserConfiguration.VRCA_CLIENT_WEB_BABYLON_JS_DEFAULT_WORLD_API_URI_USING_SSL
? `https://${clientBrowserConfiguration.VRCA_CLIENT_WEB_BABYLON_JS_DEFAULT_WORLD_API_URI}${Communication.WS_UPGRADE_PATH}`
: `http://${clientBrowserConfiguration.VRCA_CLIENT_WEB_BABYLON_JS_DEFAULT_WORLD_API_URI}${Communication.WS_UPGRADE_PATH}`,
authToken:
clientBrowserConfiguration.VRCA_CLIENT_WEB_BABYLON_JS_DEBUG_SESSION_TOKEN,
authProvider:
clientBrowserConfiguration.VRCA_CLIENT_WEB_BABYLON_JS_DEBUG_SESSION_TOKEN_PROVIDER,
debug: clientBrowserConfiguration.VRCA_CLIENT_WEB_BABYLON_JS_DEBUG,
suppress:
clientBrowserConfiguration.VRCA_CLIENT_WEB_BABYLON_JS_SUPPRESS,
reconnectAttempts: 5,
reconnectDelay: 5000,
},
});
const app = createApp(App);
// Make the Vircadia instance available to all components
app.provide(DEFAULT_VIRCADIA_INSTANCE_KEY, vircadiaWorld);
// Mount the app
app.mount("#app");
// Auto-connect to the domain server after mounting the app
vircadiaWorld.client.Utilities.Connection.connect();
This registers the client via:
app.provide(DEFAULT_VIRCADIA_INSTANCE_KEY, vircadiaWorld);
And optionally auto-connect after mounting:
vircadiaWorld.client.Utilities.Connection.connect();
Inject (useVircadiaContext)
Then, in your components, you can inject the Vircadia instance using the useVircadiaInstance
composable:
Example: /client/web_babylon_js/src/composables/useVircadiaContext.ts
// src/composables/useVircadiaContext.ts
import { inject, computed } from "vue";
import { useVircadiaInstance } from "@vircadia/world-sdk/browser/vue";
export function useVircadiaContext() {
// Inject (get) the Vircadia instance
const vircadiaWorld = inject(useVircadiaInstance());
if (!vircadiaWorld) {
throw new Error("Vircadia instance not found");
}
// Get the connection status
const connectionStatus = computed(
() => vircadiaWorld.connectionInfo.value.status || "disconnected",
);
return {
// Return the Vircadia instance
vircadiaWorld,
// Return the connection status
connectionStatus,
};
}
Composable
Asset (useVircadiaAsset)
In our client, we have a composable that manages the environment assets. It leverages the useVircadiaAsset
composable:
Example: /client/web_babylon_js/src/composables/useEnvironmentLoader.ts
import { ref } from "vue";
import { HDRCubeTexture, type Scene } from "@babylonjs/core";
import { useAsset } from "@vircadia/world-sdk/browser/vue";
import { useVircadiaContext } from "./useVircadiaContext";
// ^ Vircadia’s Vue SDK:
// - useAsset: composable to fetch an asset and process from Vircadia’s database.
// - useVircadiaContext: composable to get the Vircadia World client instance.
// ───────── Environment loader composable ─────────
// Loads multiple HDR environments into a Babylon.js scene.
// - Reactive loading state
// - Handles Vircadia asset fetching
// - Applies HDR textures to the scene
export function useEnvironmentLoader(
hdrFiles: string[], // list of HDR file names on the Vircadia server in the entity.entity_assets table
) {
const isLoading = ref(false);
// ^ tracks whether we’re already loading—prevents duplicate calls
const { vircadiaWorld } = useVircadiaContext();
// ^ Vircadia World client instance used by useAsset
async function loadAll(scene: Scene) {
// ^ the main entry point: loads each HDR file in sequence
if (!scene) {
console.error("Scene not found");
return;
}
if (isLoading.value) {
console.error("Environment loader already in progress");
return;
}
isLoading.value = true;
try {
for (const fileName of hdrFiles) {
// ───────── Vircadia asset fetch ─────────
const asset = useAsset({
fileName: ref(fileName), // reactively watch the file name
instance: vircadiaWorld, // the Vircadia World client connection
useCache: true, // cache the download in IndexedDB
});
await asset.executeLoad(); // actually fetches the blob from Vircadia’s assets
// once loaded, the SDK exposes a blob URL we can feed into Babylon
const url = asset.assetData.value?.blobUrl;
if (!url) throw new Error(`Failed to load ${fileName}`);
// ───────── Babylon.js HDR setup ─────────
const hdr = new HDRCubeTexture(
url, // the blob URL
scene, // attach to our scene
512, // size of each cube face
false, // no generateMipMaps
true, // invertY
false, // no async generation
true, // prefiltered
);
// wait for the texture to finish loading internally
await new Promise<void>((resolve) =>
hdr.onLoadObservable.addOnce(() => resolve()),
);
// apply it as the scene’s environment (lighting + skybox)
scene.environmentTexture = hdr;
scene.environmentIntensity = 1.2;
scene.createDefaultSkybox(hdr, true, 1000);
}
} catch (e) {
console.error(e); // log any Vircadia-load or Babylon errors
} finally {
isLoading.value = false;
}
}
return { isLoading, loadAll };
// ^ expose to your component so you can trigger loadAll() and react to isLoading
}
Entity (useVircadiaEntity)
In our client, we have a composable that manages the avatar entity. It leverages the useVircadiaEntity
composable:
Example: /client/web_babylon_js/src/composables/useAvatarEntity.ts
import { ref, watch, type Ref } from "vue";
import { useThrottleFn } from "@vueuse/core";
import { useEntity } from "@vircadia/world-sdk/browser/vue";
import type { ZodSchema } from "zod";
// ───────── Avatar entity composable ─────────
// Manages retrieval, creation, and metadata updates for an avatar entity in Vircadia World.
// - Retrieves or creates the entity with initial metadata validated by Zod.
// - Exposes loading and error states.
// - Provides a throttled update function for metadata changes.
export function useAvatarEntity<M>(
entityNameRef: Ref<string>,
throttleInterval: number,
metaDataSchema: ZodSchema<M>,
getInitialMeta: () => M,
getCurrentMeta: () => M,
) {
const avatarEntity = useEntity({
// A reactive ref holding the avatar's unique name; triggers re-fetch/create on change.
entityName: entityNameRef,
// SQL SELECT fragment; load any columns needed for usage.
selectClause: "general__entity_name, meta__data",
// SQL INSERT snippet; sets the entity with the given values if needed.
insertClause:
"(general__entity_name, meta__data) VALUES ($1, $2) RETURNING general__entity_name",
// Values for INSERT: the values to insert into the entity.
insertParams: [entityNameRef.value, JSON.stringify(getInitialMeta())],
// Zod schema for parsing and typing the metadata on retrieval.
metaDataSchema,
// Fallback metadata used if parsing fails or before creation.
defaultMetaData: getInitialMeta(),
});
const isLoading = ref(false);
const hasError = ref(false);
const errorMessage = ref<string>("");
// Reflect retrieving/creating/updating state
watch(
[
() => avatarEntity.retrieving.value,
() => avatarEntity.creating.value,
() => avatarEntity.updating.value,
],
([retrieving, creating, updating]) => {
isLoading.value = retrieving || creating || updating;
},
{ immediate: true },
);
// Reflect errors
watch(
() => avatarEntity.error.value,
(error) => {
hasError.value = !!error;
errorMessage.value = error ? `Entity error: ${error}` : "";
},
);
// Throttled update
const throttledUpdate = useThrottleFn(async () => {
if (!avatarEntity.entityData.value?.general__entity_name) {
return;
}
const updatedMeta = getCurrentMeta();
await avatarEntity.executeUpdate("meta__data = $1", [
JSON.stringify(updatedMeta),
]);
}, throttleInterval);
return { avatarEntity, isLoading, hasError, errorMessage, throttledUpdate };
}