Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pinning/Unpinning interaction and media promotion checks for bitECS + entity state API #6092

Merged
merged 29 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5525b2e
Functioning pin behavior sans permissions
May 18, 2023
28e3138
Pinned component add/remove based on network response
May 19, 2023
938806f
Only display pin if file is remote or owned
May 19, 2023
767acb6
Persist pinned component in temporary place for testing
May 19, 2023
97e6313
Add Pinned component during room join on items created by reticulum
May 22, 2023
61cd04f
Remove pinnable system
May 22, 2023
713881a
Clean-up and more performant button visibility checks
May 23, 2023
37603ba
Access APP less, relegate sign-in prompts to TODOs
May 23, 2023
f3758df
Merge branch 'master' into ecs-pin-permissions
May 23, 2023
2106e2b
Remove accidental import
May 23, 2023
5fac68d
Fix for misunderstanding about object menu rendering
May 24, 2023
f61ee78
Move file information from MediaLoaded to new FileInfo component
May 24, 2023
4ae4721
Move pin permissions logic out of ObjectMenu system
May 24, 2023
29fe0ec
Check for necessary components in setPinned
May 25, 2023
335432d
Handle public links in isPinnable
May 25, 2023
8febdbd
Double bang to force flag to boolean
May 25, 2023
5623381
Use EntityID type and move payload construction to createEntityStateP…
May 26, 2023
c60d1ca
prettier ignore on loaderForMediaType, correct delimiter
Jun 1, 2023
96e456b
Remove leftover line from MediaLoaded data handling
Jun 1, 2023
0fca373
Derive state based on network messages, remove FileInfo
Jun 1, 2023
93cf42d
Clean-up unnecessary changes and remove Pinnable component
Jun 2, 2023
8fcc847
Remove Pinnable and Pinned component to prevent confusion, modify how…
Jun 2, 2023
aaa9df3
Use alias to fix naming collision between react state and utility fun…
Jun 2, 2023
39d3b83
Passing file id in order to set inactive
Jun 2, 2023
826ec75
Save entity state for legacy objects
Jun 13, 2023
11594ae
Store pending legacy object saves in dedicated array
Jun 14, 2023
f2e69d7
Merge remote-tracking branch 'origin/master' into ecs-pin-permissions
johnshaughnessy Jun 29, 2023
33dca22
Create entity_state for legacy room objects immediately
johnshaughnessy Jul 1, 2023
3769682
Merge remote-tracking branch 'origin/master' into ecs-pin-permissions
johnshaughnessy Jul 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .defaults.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ THUMBNAIL_SERVER="nearspark-dev.reticulum.io"
ASSET_BUNDLE_SERVER="https://asset-bundles-prod.reticulum.io"

# Comma-separated list of domains which are known to not need CORS proxying
NON_CORS_PROXY_DOMAINS="hubs.local,dev.reticulum.io,hubs-upload-cdn.com"
NON_CORS_PROXY_DOMAINS="hubs.local,hubs-proxy.local,dev.reticulum.io,hubs-upload-cdn.com"

# The root URL under which Hubs expects static assets to be served.
BASE_ASSETS_PATH=/
Expand Down
6 changes: 3 additions & 3 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ export const PhysicsShape = defineComponent({
heightfieldDistance: Types.f32,
flags: Types.ui8
});
export const Pinnable = defineComponent();
export const Pinned = defineComponent();
export const DestroyAtExtremeDistance = defineComponent();
export const MediaLoading = defineComponent();
export const FloatyObject = defineComponent({ flags: Types.ui8, releaseGravity: Types.f32 });
Expand Down Expand Up @@ -153,9 +151,11 @@ export const CameraTool = defineComponent({
export const MyCameraTool = defineComponent();
export const MediaLoader = defineComponent({
src: Types.ui32,
flags: Types.ui8
flags: Types.ui8,
fileId: Types.ui32
});
MediaLoader.src[$isStringType] = true;
MediaLoader.fileId[$isStringType] = true;
export const MediaLoaded = defineComponent();
export const MediaContentBounds = defineComponent({
bounds: [Types.f32, 3]
Expand Down
24 changes: 10 additions & 14 deletions src/bit-systems/media-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,19 @@ export function* waitForMediaLoaded(world: HubsWorld, eid: EntityID) {
}
}

// prettier-ignore
const loaderForMediaType = {
[MediaType.IMAGE]: (
world: HubsWorld,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadImage(world, accessibleUrl, contentType),
[MediaType.VIDEO]: (
world: HubsWorld,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadVideo(world, accessibleUrl, contentType),
[MediaType.MODEL]: (
world: HubsWorld,
{ accessibleUrl, contentType }: { accessibleUrl: string; contentType: string }
) => loadModel(world, accessibleUrl, contentType, true),
[MediaType.PDF]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) => loadPDF(world, accessibleUrl),
[MediaType.IMAGE]: (world: HubsWorld, { accessibleUrl, contentType }: { accessibleUrl: string, contentType: string }) =>
loadImage(world, accessibleUrl, contentType),
[MediaType.VIDEO]: (world: HubsWorld, { accessibleUrl, contentType }: { accessibleUrl: string, contentType: string }) =>
loadVideo(world, accessibleUrl, contentType),
[MediaType.MODEL]: (world: HubsWorld, { accessibleUrl, contentType }: { accessibleUrl: string, contentType: string }) =>
loadModel(world, accessibleUrl, contentType, true),
[MediaType.PDF]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) =>
loadPDF(world, accessibleUrl),
[MediaType.AUDIO]: (world: HubsWorld, { accessibleUrl }: { accessibleUrl: string }) =>
loadAudio(world, accessibleUrl),
[MediaType.HTML]: (world: HubsWorld, { canonicalUrl, thumbnail }: { canonicalUrl: string; thumbnail: string }) =>
[MediaType.HTML]: (world: HubsWorld, { canonicalUrl, thumbnail }: { canonicalUrl: string, thumbnail: string }) =>
loadHtml(world, canonicalUrl, thumbnail)
};

Expand Down
3 changes: 1 addition & 2 deletions src/bit-systems/network-receive-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { addComponent, defineQuery, enterQuery, hasComponent, removeComponent, r
import { HubsWorld } from "../app";
import { Networked, Owned } from "../bit-components";
import { renderAsNetworkedEntity } from "../utils/create-networked-entity";
import { deleteEntityState, hasSavedEntityState } from "../utils/entity-state-utils";
import { createEntityState, deleteEntityState, hasSavedEntityState } from "../utils/entity-state-utils";
import { networkableComponents, schemas, StoredComponent } from "../utils/network-schemas";
import type { ClientID, CursorBufferUpdateMessage, EntityID, StringID, UpdateMessage } from "../utils/networking-types";
import { hasPermissionToSpawn } from "../utils/permissions";
Expand Down Expand Up @@ -141,7 +141,6 @@ export function networkReceiveSystem(world: HubsWorld) {
world.ignoredNids.add(nid);
continue;
}

renderAsNetworkedEntity(world, prefabName, initialData, nidString, creator);
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/bit-systems/networking.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { defineQuery } from "bitecs";
import { Networked } from "../bit-components";
import type { CreateMessageData, CreatorChange, EntityID, Message, StringID } from "../utils/networking-types";
import type {
CreateMessageData,
CreatorChange,
EntityID,
Message,
NetworkID,
StringID
} from "../utils/networking-types";
export let localClientID: StringID | null = null;
export function setLocalClientID(clientId: StringID) {
connectedClientIds.add(clientId);
Expand Down
9 changes: 5 additions & 4 deletions src/bit-systems/object-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
} from "../bit-components";
import { anyEntityWith, findAncestorWithComponent } from "../utils/bit-utils";
import { createNetworkedEntity } from "../utils/create-networked-entity";
import { createEntityState, deleteEntityState } from "../utils/entity-state-utils";
import HubChannel from "../utils/hub-channel";
import type { EntityID } from "../utils/networking-types";
import { setMatrixWorld } from "../utils/three-utils";
import { deleteTheDeletableAncestor } from "./delete-entity-system";
import { createMessageDatas, isPinned } from "./networking";
import { TRANSFORM_MODE } from "../components/transform-object-button";
import { ScalingHandler } from "../components/scale-button";
import { canPin, setPinned } from "../utils/bit-pinning-helper";

// Working variables.
const _vec3_1 = new Vector3();
Expand Down Expand Up @@ -148,9 +148,9 @@ function cloneObject(world: HubsWorld, sourceEid: EntityID) {

function handleClicks(world: HubsWorld, menu: EntityID, hubChannel: HubChannel) {
if (clicked(world, ObjectMenu.pinButtonRef[menu])) {
createEntityState(hubChannel, world, ObjectMenu.targetRef[menu]);
setPinned(hubChannel, world, ObjectMenu.targetRef[menu], true);
} else if (clicked(world, ObjectMenu.unpinButtonRef[menu])) {
deleteEntityState(hubChannel, world, ObjectMenu.targetRef[menu]);
setPinned(hubChannel, world, ObjectMenu.targetRef[menu], false);
} else if (clicked(world, ObjectMenu.cameraFocusButtonRef[menu])) {
console.log("Clicked focus");
} else if (clicked(world, ObjectMenu.cameraTrackButtonRef[menu])) {
Expand Down Expand Up @@ -203,8 +203,9 @@ function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) {
const obj = world.eid2obj.get(menu)!;
obj.visible = visible;

world.eid2obj.get(ObjectMenu.pinButtonRef[menu])!.visible = visible && !isPinned(target);
world.eid2obj.get(ObjectMenu.unpinButtonRef[menu])!.visible = visible && isPinned(target);
world.eid2obj.get(ObjectMenu.pinButtonRef[menu])!.visible =
visible && !isPinned(target) && canPin(APP.hubChannel!, world, target);

[
ObjectMenu.cameraFocusButtonRef[menu],
Expand Down
18 changes: 14 additions & 4 deletions src/components/media-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
proxiedUrlFor,
isHubsRoomUrl,
isLocalHubsSceneUrl,
isLocalHubsAvatarUrl
isLocalHubsAvatarUrl,
isHubsDestinationUrl,
isHubsAvatarUrl,
hubsRoomRegex,
localHubsRoomRegex
} from "../utils/media-url-utils";
import { addAnimationComponents } from "../utils/animation";

Expand Down Expand Up @@ -363,10 +367,16 @@ AFRAME.registerComponent("media-loader", {

// We want to resolve and proxy some hubs urls, like rooms and scene links,
// but want to avoid proxying assets in order for this to work in dev environments
const isLocalModelAsset =
isNonCorsProxyDomain(parsedUrl.hostname) && (guessContentType(src) || "").startsWith("model/gltf");
const isLocalAsset =
isNonCorsProxyDomain(parsedUrl.hostname) &&
!(await isHubsDestinationUrl(src)) &&
!(await isHubsAvatarUrl(src)) &&
!src.match(hubsRoomRegex)?.groups.id &&
!src.match(localHubsRoomRegex)?.groups.id;

if (this.data.resolve && !src.startsWith("data:") && !src.startsWith("hubs:") && !isLocalModelAsset) {
console.log("IS LOCAL ASSET?", isLocalAsset, src);

if (this.data.resolve && !src.startsWith("data:") && !src.startsWith("hubs:") && !isLocalAsset) {
const is360 = !!(this.data.mediaOptions.projection && this.data.mediaOptions.projection.startsWith("360"));
const quality = getDefaultResolveQuality(is360);
const result = await resolveUrl(src, quality, version, forceLocalRefresh);
Expand Down
11 changes: 0 additions & 11 deletions src/components/pinnable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { addComponent, removeComponent } from "bitecs";
import { Pinnable, Pinned } from "../bit-components";

AFRAME.registerComponent("pinnable", {
schema: {
pinned: { default: false }
Expand All @@ -16,18 +13,10 @@ AFRAME.registerComponent("pinnable", {
this.el.addEventListener("owned-pager-page-changed", this._persist);

this.el.addEventListener("owned-video-state-changed", this._persistAndAnimate);

addComponent(APP.world, Pinnable, this.el.object3D.eid);
},

update() {
this._animate();

if (this.data.pinned) {
addComponent(APP.world, Pinned, this.el.object3D.eid);
} else {
removeComponent(APP.world, Pinned, this.el.object3D.eid);
}
},

_persistAndAnimate() {
Expand Down
6 changes: 5 additions & 1 deletion src/inflators/media-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ export type MediaLoaderParams = {
resize: boolean;
recenter: boolean;
animateLoad: boolean;
fileId?: string;
isObjectMenuTarget: boolean;
};

export function inflateMediaLoader(
world: HubsWorld,
eid: number,
{ src, recenter, resize, animateLoad, isObjectMenuTarget }: MediaLoaderParams
{ src, recenter, resize, animateLoad, fileId, isObjectMenuTarget }: MediaLoaderParams
) {
addComponent(world, MediaLoader, eid);
let flags = 0;
Expand All @@ -23,5 +24,8 @@ export function inflateMediaLoader(
if (animateLoad) flags |= MEDIA_LOADER_FLAGS.ANIMATE_LOAD;
if (isObjectMenuTarget) flags |= MEDIA_LOADER_FLAGS.IS_OBJECT_MENU_TARGET;
MediaLoader.flags[eid] = flags;
if (fileId) {
MediaLoader.fileId[eid] = APP.getSid(fileId)!;
}
MediaLoader.src[eid] = APP.getSid(src)!;
}
2 changes: 2 additions & 0 deletions src/load-media-on-paste-or-drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export async function spawnFromFileList(files: FileList) {
recenter: true,
resize: !qsTruthy("noResize"),
animateLoad: true,
fileId: response.file_id,
isObjectMenuTarget: true
};
})
Expand All @@ -61,6 +62,7 @@ export async function spawnFromFileList(files: FileList) {
recenter: true,
resize: !qsTruthy("noResize"),
animateLoad: true,
fileId: null,
stalgiag marked this conversation as resolved.
Show resolved Hide resolved
isObjectMenuTarget: true
};
});
Expand Down
15 changes: 6 additions & 9 deletions src/react-components/room/object-hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ import { removeNetworkedObject } from "../../utils/removeNetworkedObject";
import { rotateInPlaceAroundWorldUp, affixToWorldUp } from "../../utils/three-utils";
import { getPromotionTokenForFile } from "../../utils/media-utils";
import { hasComponent } from "bitecs";
import { Pinnable, Pinned, Static } from "../../bit-components";

function getPinnedState(el) {
return !!(hasComponent(APP.world, Pinnable, el.eid) && hasComponent(APP.world, Pinned, el.eid));
}
import { Static } from "../../bit-components";
import { isPinned as getPinnedState } from "../../bit-systems/networking";

export function isMe(object) {
return object.el.id === "avatar-rig";
Expand All @@ -31,7 +28,7 @@ export function getObjectUrl(object) {
}

export function usePinObject(hubChannel, scene, object) {
const [isPinned, setIsPinned] = useState(getPinnedState(object.el));
const [isPinned, setIsPinned] = useState(getPinnedState(object.el.eid));

const pinObject = useCallback(() => {
const el = object.el;
Expand All @@ -57,11 +54,11 @@ export function usePinObject(hubChannel, scene, object) {
const el = object.el;

function onPinStateChanged() {
setIsPinned(getPinnedState(el));
setIsPinned(getPinnedState(el.eid));
}
el.addEventListener("pinned", onPinStateChanged);
el.addEventListener("unpinned", onPinStateChanged);
setIsPinned(getPinnedState(el));
setIsPinned(getPinnedState(el.eid));
return () => {
el.removeEventListener("pinned", onPinStateChanged);
el.removeEventListener("unpinned", onPinStateChanged);
Expand Down Expand Up @@ -125,7 +122,7 @@ export function useRemoveObject(hubChannel, scene, object) {
const canRemoveObject = !!(
scene.is("entered") &&
!isPlayer(object) &&
!getPinnedState(el) &&
!getPinnedState(el.eid) &&
!hasComponent(APP.world, Static, el.eid) &&
hubChannel.can("spawn_and_move_media")
);
Expand Down
4 changes: 2 additions & 2 deletions src/systems/hold-system.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { addComponent, removeComponent, defineQuery, hasComponent } from "bitecs
import {
Held,
Holdable,
Pinned,
HoveredRemoteRight,
HeldRemoteRight,
HoveredRemoteLeft,
Expand All @@ -15,6 +14,7 @@ import {
AEntity
} from "../bit-components";
import { canMove } from "../utils/permissions-utils";
import { isPinned } from "../bit-systems/networking";

const GRAB_REMOTE_RIGHT = paths.actions.cursor.right.grab;
const DROP_REMOTE_RIGHT = paths.actions.cursor.right.drop;
Expand All @@ -35,7 +35,7 @@ function grab(world, userinput, queryHovered, held, grabPath) {
if (
hovered &&
userinput.get(grabPath) &&
(!hasComponent(world, Pinned, hovered) || AFRAME.scenes[0].is("frozen")) &&
(!isPinned(hovered) || AFRAME.scenes[0].is("frozen")) &&
hasPermissionToGrab(world, hovered)
) {
addComponent(world, held, hovered);
Expand Down
15 changes: 3 additions & 12 deletions src/systems/userinput/devices/app-aware-touchscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,9 @@ import { findRemoteHoverTarget } from "../../../components/cursor-controller";
// import { canMove } from "../../../utils/permissions-utils";
import ResizeObserver from "resize-observer-polyfill";
import { hasComponent } from "bitecs";
import {
AEntity,
HeldRemoteRight,
OffersRemoteConstraint,
Pinnable,
Pinned,
SingleActionButton,
Static
} from "../../../bit-components";
import { AEntity, HeldRemoteRight, OffersRemoteConstraint, SingleActionButton, Static } from "../../../bit-components";
import { anyEntityWith } from "../../../utils/bit-utils";
import { isPinned } from "../../../bit-systems/networking";

const MOVE_CURSOR_JOB = "MOVE CURSOR";
const MOVE_CAMERA_JOB = "MOVE CAMERA";
Expand Down Expand Up @@ -70,14 +63,12 @@ function shouldMoveCursor(touch, rect, raycaster) {
.get(remoteHoverTarget)
.el.matches(".interactable, .interactable *, .occupiable-waypoint-icon, .teleport-waypoint-icon"));

const isPinned =
hasComponent(APP.world, Pinnable, remoteHoverTarget) && hasComponent(APP.world, Pinned, remoteHoverTarget);
const isSceneFrozen = AFRAME.scenes[0].is("frozen");

// TODO isStatic is likely a superfluous check for things matched via OffersRemoteConstraint
const isStatic = hasComponent(APP.world, Static, remoteHoverTarget);
return (
isSingleActionButton || (isInteractable && (isSceneFrozen || !isPinned) && !isStatic)
isSingleActionButton || (isInteractable && (isSceneFrozen || !isPinned(remoteHoverTarget)) && !isStatic)
// TODO check canMove
//&& (remoteHoverTarget && canMove(remoteHoverTarget))
);
Expand Down
49 changes: 49 additions & 0 deletions src/utils/bit-pinning-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { HubsWorld } from "../app";
import { createEntityState, deleteEntityState } from "./entity-state-utils";
import HubChannel from "./hub-channel";
import { EntityID } from "./networking-types";
import { takeOwnership } from "./take-ownership";
import { createMessageDatas, isNetworkInstantiated, isPinned } from "../bit-systems/networking";

export const setPinned = async (hubChannel: HubChannel, world: HubsWorld, eid: EntityID, shouldPin: boolean) => {
_signInAndPinOrUnpinElement(hubChannel, world, eid, shouldPin);
};

const _pinElement = async (hubChannel: HubChannel, world: HubsWorld, eid: EntityID) => {
try {
await createEntityState(hubChannel, world, eid);
takeOwnership(world, eid);
} catch (e) {
if (e.reason === "invalid_token") {
// TODO: Sign out and sign in again
console.log("PinningHelper: Pin failed due to invalid token, signing out and trying again", e);
} else {
console.warn("PinningHelper: Pin failed for unknown reason", e);
}
}
};

const unpinElement = (hubChannel: HubChannel, world: HubsWorld, eid: EntityID) => {
deleteEntityState(hubChannel, world, eid);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the way legacy room objects are handled, I think we should createEntityState in response to loading legacy room objects. Otherwise, when a client loads a legacy room object and synthesizes a "fake" create entity message for it, if the client moves the entity around, it will send "update" messages to reticulum, and reticulum will reject them (because it would have no stored entity state for it). This might be wasteful, so we may consider making an atomic "move RoomObject to EntityState" method later. But because I am concerned about an error causing us to lose information, I thought to do this non-destructively first. (And then delete both the RoomObject and the EntityState when an element is unpinned.)

};

const _signInAndPinOrUnpinElement = (hubChannel: HubChannel, world: HubsWorld, eid: EntityID, shouldPin: boolean) => {
const action = shouldPin ? () => _pinElement(hubChannel, world, eid) : () => unpinElement(hubChannel, world, eid);
// TODO: Perform conditional sign in
action();
};

export const canPin = (hubChannel: HubChannel, world: HubsWorld, eid: EntityID): boolean => {
const {
initialData: { fileId }
} = createMessageDatas.get(eid)!;
const hasFile = !!fileId;
const hasPromotableFile =
hasFile && APP.store.state.uploadPromotionTokens.some((upload: any) => upload.fileId === fileId);
return (
isNetworkInstantiated(eid) &&
!isPinned(eid) &&
hubChannel.can("pin_objects") && // TODO: Remove once conditional sign in is implemented
(!hasFile || hasPromotableFile)
);
};
Loading