Skip to content

Commit

Permalink
Initial add-ons API commit
Browse files Browse the repository at this point in the history
  • Loading branch information
keianhzo committed Feb 12, 2024
1 parent a8208fc commit 5ee0ba8
Show file tree
Hide file tree
Showing 26 changed files with 633 additions and 158 deletions.
3 changes: 3 additions & 0 deletions addons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"addons": ["hubs-duck-addon", "hubs-portals-addon"]
}
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@
"history": "^4.7.2",
"hls.js": "^0.14.6",
"html2canvas": "^1.0.0-rc.7",
"hubs-duck-addon": "github:MozillaReality/hubs-duck-addon",
"hubs-portals-addon": "github:MozillaReality/hubs-portals-addon",
"js-cookie": "^2.2.0",
"jsonschema": "^1.2.2",
"jwt-decode": "^2.2.0",
Expand Down
184 changes: 184 additions & 0 deletions src/addons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { App } from "./app";
import { prefabs } from "./prefabs/prefabs";

import {
InflatorConfigT,
SystemConfigT,
SystemOrderE,
PrefabConfigT,
NetworkSchemaConfigT,
ChatCommandConfigT
} from "./types";
import configs from "./utils/configs";
import { commonInflators, gltfInflators, jsxInflators } from "./utils/jsx-entity";
import { networkableComponents, schemas } from "./utils/network-schemas";

function getNextIdx(slot: Array<SystemConfigT>, system: SystemConfigT) {
return slot.findIndex(item => {
item.order > system.order;
});
}

function registerSystem(system: SystemConfigT) {
let slot = APP.addon_systems.prePhysics;
if (system.order < SystemOrderE.PrePhysics) {
slot = APP.addon_systems.setup;
} else if (system.order < SystemOrderE.PostPhysics) {
slot = APP.addon_systems.prePhysics;
} else if (system.order < SystemOrderE.MatricesUpdate) {
slot = APP.addon_systems.postPhysics;
} else if (system.order < SystemOrderE.BeforeRender) {
slot = APP.addon_systems.beforeRender;
} else if (system.order < SystemOrderE.AfterRender) {
slot = APP.addon_systems.afterRender;
} else if (system.order < SystemOrderE.PostProcessing) {
slot = APP.addon_systems.postProcessing;
} else {
slot = APP.addon_systems.tearDown;
}
const nextIdx = getNextIdx(slot, system);
slot.splice(nextIdx, 0, system);
}

function registerInflator(inflator: InflatorConfigT) {
if (inflator.common) {
commonInflators[inflator.common.id] = inflator.common.inflator;
} else {
if (inflator.jsx) {
jsxInflators[inflator.jsx.id] = inflator.jsx.inflator;
}
if (inflator.gltf) {
gltfInflators[inflator.gltf.id] = inflator.gltf.inflator;
}
}
}

function registerPrefab(prefab: PrefabConfigT) {
if (prefabs.has(prefab.id)) {
throw Error(`Error registering prefab ${name}: prefab already registered`);
}
prefabs.set(prefab.id, prefab.config);
}

function registerNetworkSchema(schemaConfig: NetworkSchemaConfigT) {
if (schemas.has(schemaConfig.component)) {
throw Error(
`Error registering network schema ${schemaConfig.schema.componentName}: network schema already registered`
);
}
schemas.set(schemaConfig.component, schemaConfig.schema);
networkableComponents.push(schemaConfig.component);
}

function registerChatCommand(command: ChatCommandConfigT) {
APP.messageDispatch.registerChatCommand(command.id, command.command);
}

export type AddonIdT = string;
export type AddonNameT = string;
export type AddonDescriptionT = string;
export type AddonOnReadyFn = (app: App, config?: JSON) => void;

export interface InternalAddonConfigT {
name: AddonNameT;
description?: AddonDescriptionT;
onReady?: AddonOnReadyFn;
system?: SystemConfigT | SystemConfigT[];
inflator?: InflatorConfigT | InflatorConfigT[];
prefab?: PrefabConfigT | PrefabConfigT[];
networkSchema?: NetworkSchemaConfigT | NetworkSchemaConfigT[];
chatCommand?: ChatCommandConfigT | ChatCommandConfigT[];
enabled?: boolean;
config?: JSON | undefined;
}
type AddonConfigT = Omit<InternalAddonConfigT, "enabled" | "config">;

const pendingAddons = new Map<AddonIdT, InternalAddonConfigT>();
export const addons = new Map<AddonIdT, AddonConfigT>();
export type AddonRegisterCallbackT = (app: App) => void;
export function registerAddon(id: AddonIdT, config: AddonConfigT) {
console.log(`Add-on ${id} registered`);
pendingAddons.set(id, config);
}

export function onAddonsInit(app: App) {
app.scene?.addEventListener("hub_updated", () => {
for (const [id, addon] of pendingAddons) {
if (addons.has(id)) {
throw Error(`Addon ${id} already registered`);
} else {
addons.set(id, addon);
}

if (app.hub?.user_data && `addon_${id}` in app.hub.user_data) {
addon.enabled = app.hub.user_data[`addon_${id}`];
} else {
addon.enabled = false;
}

if (!addon.enabled) {
continue;
}

if (addon.prefab) {
if (Array.isArray(addon.prefab)) {
addon.prefab.forEach(prefab => {
registerPrefab(prefab);
});
} else {
registerPrefab(addon.prefab);
}
}

if (addon.networkSchema) {
if (Array.isArray(addon.networkSchema)) {
addon.networkSchema.forEach(networkSchema => {
registerNetworkSchema(networkSchema);
});
} else {
registerNetworkSchema(addon.networkSchema);
}
}

if (addon.inflator) {
if (Array.isArray(addon.inflator)) {
addon.inflator.forEach(inflator => {
registerInflator(inflator);
});
} else {
registerInflator(addon.inflator);
}
}

if (addon.system) {
if (Array.isArray(addon.system)) {
addon.system.forEach(system => {
registerSystem(system);
});
} else {
registerSystem(addon.system);
}
}

if (addon.chatCommand) {
if (Array.isArray(addon.chatCommand)) {
addon.chatCommand.forEach(chatCommand => {
registerChatCommand(chatCommand);
});
} else {
registerChatCommand(addon.chatCommand);
}
}

if (addon.onReady) {
let config;
const addonsConfig = configs.feature("addons_config");
if (addonsConfig && id in addonsConfig) {
config = addonsConfig[id];
}
addon.onReady(app, config);
}
}
pendingAddons.clear();
});
}
29 changes: 28 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import SceneEntryManager from "./scene-entry-manager";
import { store } from "./utils/store-instance";
import { addObject3DComponent } from "./utils/jsx-entity";
import { ElOrEid } from "./utils/bit-utils";
import { onAddonsInit } from "./addons";
import { CoreSystemKeyT, HubsSystemKeyT, SystemConfigT, SystemKeyT, SystemT } from "./types";

declare global {
interface Window {
Expand Down Expand Up @@ -63,7 +65,7 @@ export function getScene() {
return promiseToScene;
}

interface HubDescription {
export interface HubDescription {
hub_id: string;
user_data?: any;
}
Expand Down Expand Up @@ -104,6 +106,18 @@ export class App {

dialog = new DialogAdapter();

addon_systems = {
setup: new Array<{ order: number; system: SystemT }>(),
prePhysics: new Array<{ order: number; system: SystemT }>(),
postPhysics: new Array<{ order: number; system: SystemT }>(),
matricesUpdate: new Array<{ order: number; system: SystemT }>(),
beforeRender: new Array<{ order: number; system: SystemT }>(),
render: new Array<{ order: number; system: SystemT }>(),
afterRender: new Array<{ order: number; system: SystemT }>(),
postProcessing: new Array<{ order: number; system: SystemT }>(),
tearDown: new Array<{ order: number; system: SystemT }>()
};

RENDER_ORDER = {
HUD_BACKGROUND: 1,
HUD_ICONS: 2,
Expand Down Expand Up @@ -159,6 +173,19 @@ export class App {
return this.sid2str.get(sid);
}

notifyOnInit() {
onAddonsInit(this);
}

getSystem(id: SystemKeyT) {
const systems = this.scene?.systems!;
if (id in systems) {
return systems[id as CoreSystemKeyT];
} else {
return systems["hubs-systems"][id as HubsSystemKeyT];
}
}

// This gets called by a-scene to setup the renderer, camera, and audio listener
// TODO ideally the contorl flow here would be inverted, and we would setup this stuff,
// initialize aframe, and then run our own RAF loop
Expand Down
1 change: 0 additions & 1 deletion src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,6 @@ export const LinearScale = defineComponent({
targetY: Types.f32,
targetZ: Types.f32
});
export const Quack = defineComponent();
export const TrimeshTag = defineComponent();
export const HeightFieldTag = defineComponent();
export const LocalAvatar = defineComponent();
Expand Down
18 changes: 0 additions & 18 deletions src/bit-systems/quack.ts

This file was deleted.

32 changes: 28 additions & 4 deletions src/hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ import { exposeBitECSDebugHelpers } from "./bitecs-debug-helpers";
import { loadLegacyRoomObjects } from "./utils/load-legacy-room-objects";
import { loadSavedEntityStates } from "./utils/entity-state-utils";
import { shouldUseNewLoader } from "./utils/bit-utils";
import { addons } from "./addons";

const PHOENIX_RELIABLE_NAF = "phx-reliable";
NAF.options.firstSyncSource = PHOENIX_RELIABLE_NAF;
Expand Down Expand Up @@ -543,8 +544,18 @@ export async function updateEnvironmentForHub(hub, entryManager) {
}
}

export async function updateUIForHub(hub, hubChannel, showBitECSBasedClientRefreshPrompt = false) {
remountUI({ hub, entryDisallowed: !hubChannel.canEnterRoom(hub), showBitECSBasedClientRefreshPrompt });
export async function updateUIForHub(
hub,
hubChannel,
showBitECSBasedClientRefreshPrompt = false,
showAddonRefreshPrompt = false
) {
remountUI({
hub,
entryDisallowed: !hubChannel.canEnterRoom(hub),
showBitECSBasedClientRefreshPrompt,
showAddonRefreshPrompt
});
}

function onConnectionError(entryManager, connectError) {
Expand Down Expand Up @@ -1388,16 +1399,27 @@ document.addEventListener("DOMContentLoaded", async () => {
const displayName = (userInfo && userInfo.metas[0].profile.displayName) || "API";

let showBitECSBasedClientRefreshPrompt = false;

if (!!hub.user_data?.hubs_use_bitecs_based_client !== !!window.APP.hub.user_data?.hubs_use_bitecs_based_client) {
showBitECSBasedClientRefreshPrompt = true;
setTimeout(() => {
document.location.reload();
}, 5000);
}
let showAddonRefreshPrompt = false;
[...addons.keys()].map(id => {
const key = `addon_${id}`;
const oldAddonState = !!window.APP.hub.user_data && window.APP.hub.user_data[key];
const newAddonState = !!hub.user_data && hub.user_data[key];
if (newAddonState !== oldAddonState) {
showAddonRefreshPrompt = true;
setTimeout(() => {
document.location.reload();
}, 5000);
}
});

window.APP.hub = hub;
updateUIForHub(hub, hubChannel, showBitECSBasedClientRefreshPrompt);
updateUIForHub(hub, hubChannel, showBitECSBasedClientRefreshPrompt, showAddonRefreshPrompt);

if (
stale_fields.includes("scene") ||
Expand Down Expand Up @@ -1484,4 +1506,6 @@ document.addEventListener("DOMContentLoaded", async () => {

authChannel.setSocket(socket);
linkChannel.setSocket(socket);

APP.notifyOnInit();
});
Loading

0 comments on commit 5ee0ba8

Please sign in to comment.