Skip to content

Commit

Permalink
perf(flat-stores): fetch users info only when necessary (#1946)
Browse files Browse the repository at this point in the history
  • Loading branch information
hyrious committed Jun 5, 2023
1 parent 2430e24 commit d93ecc1
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,28 @@ export const LoginWithPhone: React.FC<LoginWithPhoneProps> = ({
<div className="login-with-phone">
<div className="login-width-limiter">
<LoginTitle />
{process.env.DEV ? (
<select
style={{
position: "absolute",
height: 32,
transform: "translateX(-120%)",
opacity: 0,
cursor: "pointer",
}}
onChange={ev => {
setPhone(ev.currentTarget.value);
setCode("666666");
setAgreed(true);
}}
>
{Array.from({ length: 9 }, (_, i) => (
<option key={i} value={"13" + String(i + 1).repeat(9)}>
13{i + 1}
</option>
))}
</select>
) : null}
<Input
placeholder={t("enter-phone")}
prefix={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
margin-right: 5px;
padding: 4px;
border-radius: 8px;
cursor: unset;
cursor: default;
z-index: 9999;

&:hover {
background-color: var(--blue-1);
Expand Down
7 changes: 6 additions & 1 deletion packages/flat-pages/src/components/UsersButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ export const UsersButton = observer<UsersButtonProps>(function UsersButton({ cla
const users = useComputed(() => {
const { offlineJoiners } = classroom;
const { speakingJoiners, handRaisingJoiners, otherJoiners } = classroom.users;
return [...speakingJoiners, ...handRaisingJoiners, ...offlineJoiners, ...otherJoiners].sort(

// speaking users may include offline users, so filter them out
const offlineUserUUIDs = new Set(offlineJoiners.map(user => user.userUUID));
const speakingOnline = speakingJoiners.filter(user => !offlineUserUUIDs.has(user.userUUID));

return [...speakingOnline, ...handRaisingJoiners, ...offlineJoiners, ...otherJoiners].sort(
(a, b) => a.name.localeCompare(b.name),
);
}).get();
Expand Down
8 changes: 3 additions & 5 deletions packages/flat-server-api/src/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,10 @@ export interface UsersInfoPayload {
usersUUID?: string[];
}

export type UserInfo = { name: string; rtcUID: number; avatarURL: string };

export type UsersInfoResult = {
[key in string]: {
name: string;
rtcUID: number;
avatarURL: string;
};
[key in string]: UserInfo;
};

export function usersInfo(payload: UsersInfoPayload): Promise<UsersInfoResult> {
Expand Down
7 changes: 6 additions & 1 deletion packages/flat-services/src/services/text-chat/commands.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { RoomStatus } from "@netless/flat-server-api";
import type { RoomStatus, UserInfo } from "@netless/flat-server-api";

/** From teacher to students */
export interface IServiceTextChatRoomCommandData {
"update-room-status": { roomUUID: string; status: RoomStatus };
"ban": { roomUUID: string; status: boolean };
"notice": { roomUUID: string; text: string };
"reward": { roomUUID: string; userUUID: string };
// Everyone, send this message on join room
// Users that in 'peers' should send back the 'users-info' command
"enter": { roomUUID: string; userUUID: string; userInfo: UserInfo; peers?: string[] };
}

export type IServiceTextChatRoomCommandNames = keyof IServiceTextChatRoomCommandData;
Expand All @@ -25,6 +28,8 @@ export interface IServiceTextChatPeerCommandData {
"request-device-response": { roomUUID: string; camera?: boolean; mic?: boolean };
/** From teacher to student */
"notify-device-off": { roomUUID: string; camera?: false; mic?: false };
/** From everyone to everyone, should send this when received 'enter' command above */
"users-info": { roomUUID: string; users: Record<string, UserInfo> };
}

export type IServiceTextChatPeerCommandNames = keyof IServiceTextChatPeerCommandData;
Expand Down
9 changes: 8 additions & 1 deletion packages/flat-services/src/services/text-chat/events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RoomStatus } from "@netless/flat-server-api";
import type { RoomStatus, UserInfo } from "@netless/flat-server-api";
import type { Remitter } from "remitter";

export interface IServiceTextChatEventData {
Expand Down Expand Up @@ -48,6 +48,13 @@ export interface IServiceTextChatEventData {
senderID: string;
deviceState: { camera?: boolean; mic?: boolean };
};
"enter": {
roomUUID: string;
userUUID: string;
userInfo: UserInfo;
peers?: string[];
};
"users-info": { roomUUID: string; userUUID: string; users: Record<string, UserInfo> };
}

export type IServiceTextChatEventNames = Extract<keyof IServiceTextChatEventData, string>;
Expand Down
112 changes: 99 additions & 13 deletions packages/flat-stores/src/classroom-store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
IServiceWhiteboard,
} from "@netless/flat-services";
import { preferencesStore } from "../preferences-store";
import { sampleSize } from "lodash-es";

export * from "./constants";
export * from "./chat-store";
Expand Down Expand Up @@ -352,15 +353,6 @@ export class ClassroomStore {
public async init(): Promise<void> {
await roomStore.syncOrdinaryRoomInfo(this.roomUUID);

if (process.env.NODE_ENV === "development") {
if (this.roomInfo && this.roomInfo.ownerUUID !== this.ownerUUID) {
(this.ownerUUID as string) = this.roomInfo.ownerUUID;
if (process.env.DEV) {
console.error(new Error("ClassRoom Error: ownerUUID mismatch!"));
}
}
}

await this.initRTC();

await this.rtm.joinRoom({
Expand All @@ -372,8 +364,6 @@ export class ClassroomStore {

const fastboard = await this.whiteboardStore.joinWhiteboardRoom();

await this.users.initUsers([this.ownerUUID, ...this.rtm.members]);

const deviceStateStorage = fastboard.syncedStore.connectStorage<DeviceStateStorageState>(
"deviceState",
{},
Expand Down Expand Up @@ -404,6 +394,65 @@ export class ClassroomStore {
this.whiteboardStorage = whiteboardStorage;
this.userWindowsStorage = userWindowsStorage;

const onStageUsers = Object.keys(onStageUsersStorage.state).filter(
userUUID => onStageUsersStorage.state[userUUID],
);
const members = [...this.rtm.members];
await this.users.initUsers(members, [this.ownerUUID, this.userUUID, ...onStageUsers]);
const owner = this.users.cachedUsers.get(this.ownerUUID);
// update owner info in room store, it will use that to render the users panel
roomStore.updateRoom(this.roomUUID, this.ownerUUID, {
ownerName: owner?.name,
ownerAvatarURL: owner?.avatar,
});

const user = this.users.cachedUsers.get(this.userUUID);
if (user) {
void this.rtm.sendRoomCommand("enter", {
roomUUID: this.roomUUID,
userUUID: user.userUUID,
userInfo: {
name: user.name,
avatarURL: user.avatar,
rtcUID: +user.rtcUID || 0,
},
peers: sampleSize(members, 3),
});
}

this.sideEffect.addDisposer(
this.rtm.events.on("enter", ({ userUUID: senderID, userInfo, peers }) => {
if (senderID === this.userUUID) {
// ignore self enter message
return;
}
if (process.env.DEV) {
console.log(`[rtm] ${senderID} is entering room with his info:`, userInfo);
}
this.users.cacheUserIfNeeded(senderID, userInfo);
if (peers && peers.includes(this.userUUID)) {
this.sendUsersInfoToPeer(senderID);
if (process.env.DEV) {
console.log(`[rtm] send local users info to peer ${senderID}`);
}
}
}),
);

this.sideEffect.addDisposer(
this.rtm.events.on("users-info", ({ userUUID: senderID, users }) => {
let count = 0;
for (const userUUID in users) {
if (this.users.cacheUserIfNeeded(userUUID, users[userUUID])) {
count++;
}
}
if (process.env.DEV) {
console.log(`[rtm] received users info from ${senderID}: %d rows`, count);
}
}),
);

if (this.isCreator) {
this.updateDeviceState(
this.userUUID,
Expand Down Expand Up @@ -457,7 +506,7 @@ export class ClassroomStore {

this.sideEffect.addDisposer(
this.rtm.events.on("member-joined", async ({ userUUID }) => {
await this.users.addUser(userUUID);
await this.users.addUser(userUUID, this.isUsersPanelVisible);
this.users.updateUsers(user => {
if (user.userUUID === userUUID) {
if (userUUID === this.ownerUUID || onStageUsersStorage.state[userUUID]) {
Expand Down Expand Up @@ -516,7 +565,7 @@ export class ClassroomStore {
const onStageUsers = Object.keys(onStageUsersStorage.state).filter(
userUUID => onStageUsersStorage.state[userUUID],
);
await this.users.syncExtraUsersInfo(onStageUsers);
await this.users.flushLazyUsers(onStageUsers);
runInAction(() => {
this.onStageUserUUIDs.replace(onStageUsers);
});
Expand Down Expand Up @@ -756,6 +805,32 @@ export class ClassroomStore {
}
}

// @TODO: use RTM 2.0 and get users info from peer properties
private sendUsersInfoToPeer(_userUUID: string): void {
// @TODO: disabled for now, be cause RTM 1.0 has a limit of 1KB per message
//
// const users: Record<string, UserInfo> = {};
//
// Filter out initialized users (whose rtcUID is not null)
// for (const user of this.users.cachedUsers.values()) {
// if (user.rtcUID) {
// users[user.userUUID] = {
// rtcUID: +user.rtcUID || 0,
// name: user.name,
// avatarURL: user.avatar,
// };
// }
// }
// void this.rtm.sendPeerCommand(
// "users-info",
// {
// roomUUID: this.roomUUID,
// users,
// },
// userUUID,
// );
}

public async destroy(): Promise<void> {
this.sideEffect.flushAll();

Expand Down Expand Up @@ -785,6 +860,10 @@ export class ClassroomStore {

public toggleUsersPanel = (visible = !this.isUsersPanelVisible): void => {
this.isUsersPanelVisible = visible;
// fetch lazy loaded users when the users panel is opened
if (visible) {
this.users.flushLazyUsers().catch(console.error);
}
};

public onDragStart = (): void => {
Expand Down Expand Up @@ -1072,6 +1151,13 @@ export class ClassroomStore {

public onToggleHandRaisingPanel = (force = !this.isHandRaisingPanelVisible): void => {
this.isHandRaisingPanelVisible = force;
// fetch lazy loaded users when the hand raising panel is opened
if (force) {
const raiseHandUsers = this.classroomStorage?.state.raiseHandUsers;
if (raiseHandUsers) {
this.users.flushLazyUsers(raiseHandUsers).catch(console.error);
}
}
};

public onToggleBan = (): void => {
Expand Down
7 changes: 0 additions & 7 deletions packages/flat-stores/src/room-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
recordInfo,
RoomStatus,
RoomType,
usersInfo,
} from "@netless/flat-server-api";
import { globalStore } from "./global-store";
import { preferencesStore } from "./preferences-store";
Expand Down Expand Up @@ -133,16 +132,10 @@ export class RoomStore {

public async syncOrdinaryRoomInfo(roomUUID: string): Promise<void> {
const { roomInfo, ...restInfo } = await ordinaryRoomInfo(roomUUID);
// always include owner avatar url in full room info
const { [roomInfo.ownerUUID]: owner } = await usersInfo({
roomUUID,
usersUUID: [roomInfo.ownerUUID],
});
this.updateRoom(roomUUID, roomInfo.ownerUUID, {
...restInfo,
...roomInfo,
roomUUID,
ownerAvatarURL: owner.avatarURL,
});
}

Expand Down

0 comments on commit d93ecc1

Please sign in to comment.