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

perf(flat-stores): fetch users info only when necessary #1946

Merged
merged 8 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading