refactor findUserByDisplayName

This commit is contained in:
pa
2026-03-14 21:43:22 +09:00
parent a314885bff
commit a64f4f6d7a
10 changed files with 195 additions and 24 deletions
+9 -4
View File
@@ -88,7 +88,8 @@ export async function tryLoadPlayerList() {
ctx.userId = ctx.userId =
findUserByDisplayName( findUserByDisplayName(
userStore.cachedUsers, userStore.cachedUsers,
ctx.displayName ctx.displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? ''; )?.id ?? '';
} }
const userMap = { const userMap = {
@@ -159,8 +160,11 @@ export function addGameLogEntry(gameLog, location) {
let userId = String(gameLog.userId || ''); let userId = String(gameLog.userId || '');
if (!userId && gameLog.displayName) { if (!userId && gameLog.displayName) {
userId = userId =
findUserByDisplayName(userStore.cachedUsers, gameLog.displayName) findUserByDisplayName(
?.id ?? ''; userStore.cachedUsers,
gameLog.displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? '';
} }
switch (gameLog.type) { switch (gameLog.type) {
case 'location-destination': case 'location-destination':
@@ -408,7 +412,8 @@ export function addGameLogEntry(gameLog, location) {
if (typeof ref2 === 'undefined') { if (typeof ref2 === 'undefined') {
const foundUser = findUserByDisplayName( const foundUser = findUserByDisplayName(
userStore.cachedUsers, userStore.cachedUsers,
gameLog.displayName gameLog.displayName,
userStore.cachedUserIdsByDisplayName
); );
if (foundUser) { if (foundUser) {
photonStore.photonLobby.set(photonId, foundUser); photonStore.photonLobby.set(photonId, foundUser);
+17 -3
View File
@@ -80,11 +80,14 @@ export function applyUser(json) {
cachedUsers, cachedUsers,
currentTravelers, currentTravelers,
customUserTags, customUserTags,
rebuildCachedUserDisplayNameIndex,
setCachedUser,
state, state,
userDialog userDialog
} = userStore; } = userStore;
let ref = cachedUsers.get(json.id); let ref = cachedUsers.get(json.id);
let previousDisplayName = '';
let hasPropChanged = false; let hasPropChanged = false;
let changedProps = {}; let changedProps = {};
sanitizeUserJson(json, getRobotUrl()); sanitizeUserJson(json, getRobotUrl());
@@ -111,18 +114,24 @@ export function applyUser(json) {
ref.$customTag = ''; ref.$customTag = '';
ref.$customTagColour = ''; ref.$customTagColour = '';
} }
evictMapCache( const { deletedCount } = evictMapCache(
cachedUsers, cachedUsers,
friendStore.friends.size + 300, friendStore.friends.size + 300,
(_value, key) => friendStore.friends.has(key), (_value, key) => friendStore.friends.has(key),
{ logLabel: 'User cache cleanup' } { logLabel: 'User cache cleanup' }
); );
cachedUsers.set(ref.id, ref); if (deletedCount > 0) {
setCachedUser(ref, '', { skipIndex: true });
rebuildCachedUserDisplayNameIndex();
} else {
setCachedUser(ref);
}
runUpdateFriendFlow(ref.id); runUpdateFriendFlow(ref.id);
} else { } else {
if (json.state !== 'online') { if (json.state !== 'online') {
runUpdateFriendFlow(ref.id, json.state); runUpdateFriendFlow(ref.id, json.state);
} }
previousDisplayName = ref.displayName;
const { hasPropChanged: _hasPropChanged, changedProps: _changedProps } = const { hasPropChanged: _hasPropChanged, changedProps: _changedProps } =
diffObjectProps(ref, json, arraysMatch); diffObjectProps(ref, json, arraysMatch);
for (const prop in json) { for (const prop in json) {
@@ -130,6 +139,7 @@ export function applyUser(json) {
ref[prop] = json[prop]; ref[prop] = json[prop];
} }
} }
setCachedUser(ref, previousDisplayName);
hasPropChanged = _hasPropChanged; hasPropChanged = _hasPropChanged;
changedProps = _changedProps; changedProps = _changedProps;
} }
@@ -637,7 +647,11 @@ export async function lookupUser(ref) {
if (!ref.displayName || ref.displayName.substring(0, 3) === 'ID:') { if (!ref.displayName || ref.displayName.substring(0, 3) === 'ID:') {
return; return;
} }
const found = findUserByDisplayName(userStore.cachedUsers, ref.displayName); const found = findUserByDisplayName(
userStore.cachedUsers,
ref.displayName,
userStore.cachedUserIdsByDisplayName
);
if (found) { if (found) {
showUserDialog(found.id); showUserDialog(found.id);
return; return;
+1 -1
View File
@@ -51,7 +51,7 @@ export function runFirstLoginFlow(ref, { now = Date.now } = {}) {
if (gameStore.isGameRunning) { if (gameStore.isGameRunning) {
ref.$previousAvatarSwapTime = now(); ref.$previousAvatarSwapTime = now();
} }
userStore.cachedUsers.clear(); // clear before running applyUser userStore.clearCachedUsers(); // clear before running applyUser
userStore.setCurrentUser(ref); userStore.setCurrentUser(ref);
authStore.loginComplete(); authStore.loginComplete();
} }
+1 -1
View File
@@ -35,7 +35,7 @@ export function clearVRCXCache() {
!locationStore.lastLocation.playerList.has(ref.id) && !locationStore.lastLocation.playerList.has(ref.id) &&
id !== userStore.currentUser.id id !== userStore.currentUser.id
) { ) {
userStore.cachedUsers.delete(id); userStore.deleteCachedUser(id);
} }
}); });
worldStore.cachedWorlds.forEach((ref, id) => { worldStore.cachedWorlds.forEach((ref, id) => {
@@ -17,6 +17,19 @@ describe('findUserByDisplayName', () => {
return map; return map;
} }
function createDisplayNameIndex(entries) {
const index = new Map();
for (const entry of entries) {
let ids = index.get(entry.displayName);
if (!ids) {
ids = new Set();
index.set(entry.displayName, ids);
}
ids.add(entry.id);
}
return index;
}
test('returns the user matching displayName', () => { test('returns the user matching displayName', () => {
const users = createCachedUsers([ const users = createCachedUsers([
{ id: 'usr_1', displayName: 'Alice' }, { id: 'usr_1', displayName: 'Alice' },
@@ -56,4 +69,31 @@ describe('findUserByDisplayName', () => {
expect(findUserByDisplayName(users, 'alice')).toBeUndefined(); expect(findUserByDisplayName(users, 'alice')).toBeUndefined();
expect(findUserByDisplayName(users, 'ALICE')).toBeUndefined(); expect(findUserByDisplayName(users, 'ALICE')).toBeUndefined();
}); });
test('uses displayName index when provided', () => {
const entries = [
{ id: 'usr_1', displayName: 'Alice' },
{ id: 'usr_2', displayName: 'Bob' }
];
const users = createCachedUsers(entries);
const index = createDisplayNameIndex(entries);
const result = findUserByDisplayName(users, 'Bob', index);
expect(result).toEqual({ id: 'usr_2', displayName: 'Bob' });
});
test('indexed lookup falls back to next duplicate when first user is missing', () => {
const entries = [
{ id: 'usr_1', displayName: 'Alice' },
{ id: 'usr_2', displayName: 'Alice' }
];
const users = createCachedUsers(entries);
users.delete('usr_1');
const index = createDisplayNameIndex(entries);
const result = findUserByDisplayName(users, 'Alice', index);
expect(result).toEqual({ id: 'usr_2', displayName: 'Alice' });
});
}); });
+15 -1
View File
@@ -275,9 +275,23 @@ function userOnlineFor(ref) {
* Find a user object from cachedUsers by displayName. * Find a user object from cachedUsers by displayName.
* @param {Map} cachedUsers * @param {Map} cachedUsers
* @param {string} displayName * @param {string} displayName
* @param {Map<string, Set<string>>} [cachedUserIdsByDisplayName]
* @returns {object|undefined} * @returns {object|undefined}
*/ */
function findUserByDisplayName(cachedUsers, displayName) { function findUserByDisplayName(
cachedUsers,
displayName,
cachedUserIdsByDisplayName
) {
const indexedUserIds = cachedUserIdsByDisplayName?.get(displayName);
if (indexedUserIds) {
for (const userId of indexedUserIds) {
const ref = cachedUsers.get(userId);
if (ref?.displayName === displayName) {
return ref;
}
}
}
for (const ref of cachedUsers.values()) { for (const ref of cachedUsers.values()) {
if (ref.displayName === displayName) { if (ref.displayName === displayName) {
return ref; return ref;
+2 -1
View File
@@ -193,7 +193,8 @@ export const useGameLogStore = defineStore('GameLog', () => {
ctx.userId = ctx.userId =
findUserByDisplayName( findUserByDisplayName(
userStore.cachedUsers, userStore.cachedUsers,
ctx.displayName ctx.displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? ''; )?.id ?? '';
} }
notificationStore.queueGameLogNoty(ctx); notificationStore.queueGameLogNoty(ctx);
+25 -10
View File
@@ -139,8 +139,11 @@ export function createMediaParsers({
let userId = ''; let userId = '';
if (displayName) { if (displayName) {
userId = userId =
findUserByDisplayName(userStore.cachedUsers, displayName)?.id ?? findUserByDisplayName(
''; userStore.cachedUsers,
displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? '';
} }
if (videoId === 'YouTube') { if (videoId === 'YouTube') {
const entry1 = { const entry1 = {
@@ -207,8 +210,11 @@ export function createMediaParsers({
let userId = ''; let userId = '';
if (displayName) { if (displayName) {
userId = userId =
findUserByDisplayName(userStore.cachedUsers, displayName)?.id ?? findUserByDisplayName(
''; userStore.cachedUsers,
displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? '';
} }
if (videoId === 'YouTube') { if (videoId === 'YouTube') {
const entry1 = { const entry1 = {
@@ -270,8 +276,11 @@ export function createMediaParsers({
let userId = ''; let userId = '';
if (displayName) { if (displayName) {
userId = userId =
findUserByDisplayName(userStore.cachedUsers, displayName)?.id ?? findUserByDisplayName(
''; userStore.cachedUsers,
displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? '';
} }
if (videoId === 'YouTube') { if (videoId === 'YouTube') {
const entry1 = { const entry1 = {
@@ -327,8 +336,11 @@ export function createMediaParsers({
let userId = ''; let userId = '';
if (displayName) { if (displayName) {
userId = userId =
findUserByDisplayName(userStore.cachedUsers, displayName)?.id ?? findUserByDisplayName(
''; userStore.cachedUsers,
displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? '';
} }
const entry1 = { const entry1 = {
created_at: gameLog.dt, created_at: gameLog.dt,
@@ -384,8 +396,11 @@ export function createMediaParsers({
let userId = ''; let userId = '';
if (displayName) { if (displayName) {
userId = userId =
findUserByDisplayName(userStore.cachedUsers, displayName)?.id ?? findUserByDisplayName(
''; userStore.cachedUsers,
displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? '';
} }
const entry1 = { const entry1 = {
created_at: gameLog.dt, created_at: gameLog.dt,
+7 -3
View File
@@ -1018,8 +1018,11 @@ export const useNotificationStore = defineStore('Notification', () => {
if (id) return id; if (id) return id;
if (noty.displayName) { if (noty.displayName) {
return ( return (
findUserByDisplayName(userStore.cachedUsers, noty.displayName) findUserByDisplayName(
?.id ?? '' userStore.cachedUsers,
noty.displayName,
userStore.cachedUserIdsByDisplayName
)?.id ?? ''
); );
} }
return ''; return '';
@@ -1086,7 +1089,8 @@ export const useNotificationStore = defineStore('Notification', () => {
} else if (noty.displayName) { } else if (noty.displayName) {
const ref = findUserByDisplayName( const ref = findUserByDisplayName(
userStore.cachedUsers, userStore.cachedUsers,
noty.displayName noty.displayName,
userStore.cachedUserIdsByDisplayName
); );
if (ref) { if (ref) {
noty.isFriend = friendStore.friends.has(ref.id); noty.isFriend = friendStore.friends.has(ref.id);
+78
View File
@@ -271,6 +271,78 @@ export const useUserStore = defineStore('User', () => {
}); });
const cachedUsers = shallowReactive(new Map()); const cachedUsers = shallowReactive(new Map());
const cachedUserIdsByDisplayName = shallowReactive(new Map());
function addCachedUserDisplayNameEntry(displayName, userId) {
if (!displayName || !userId) {
return;
}
let userIds = cachedUserIdsByDisplayName.get(displayName);
if (!userIds) {
userIds = new Set();
cachedUserIdsByDisplayName.set(displayName, userIds);
}
userIds.add(userId);
}
function removeCachedUserDisplayNameEntry(displayName, userId) {
if (!displayName || !userId) {
return;
}
const userIds = cachedUserIdsByDisplayName.get(displayName);
if (!userIds) {
return;
}
userIds.delete(userId);
if (userIds.size === 0) {
cachedUserIdsByDisplayName.delete(displayName);
}
}
function syncCachedUserDisplayName(ref, previousDisplayName = '') {
if (!ref?.id) {
return;
}
if (previousDisplayName && previousDisplayName !== ref.displayName) {
removeCachedUserDisplayNameEntry(previousDisplayName, ref.id);
}
addCachedUserDisplayNameEntry(ref.displayName, ref.id);
}
function setCachedUser(
ref,
previousDisplayName = '',
{ skipIndex = false } = {}
) {
if (!ref?.id) {
return;
}
cachedUsers.set(ref.id, ref);
if (!skipIndex) {
syncCachedUserDisplayName(ref, previousDisplayName);
}
}
function deleteCachedUser(userId) {
const ref = cachedUsers.get(userId);
if (!ref) {
return false;
}
removeCachedUserDisplayNameEntry(ref.displayName, userId);
return cachedUsers.delete(userId);
}
function clearCachedUsers() {
cachedUsers.clear();
cachedUserIdsByDisplayName.clear();
}
function rebuildCachedUserDisplayNameIndex() {
cachedUserIdsByDisplayName.clear();
for (const ref of cachedUsers.values()) {
addCachedUserDisplayNameEntry(ref.displayName, ref.id);
}
}
const isLocalUserVrcPlusSupporter = computed( const isLocalUserVrcPlusSupporter = computed(
() => currentUser.value.$isVRCPlus || AppDebug.debugVrcPlus () => currentUser.value.$isVRCPlus || AppDebug.debugVrcPlus
@@ -730,10 +802,16 @@ export const useUserStore = defineStore('User', () => {
showUserDialogHistory, showUserDialogHistory,
customUserTags, customUserTags,
cachedUsers, cachedUsers,
cachedUserIdsByDisplayName,
isLocalUserVrcPlusSupporter, isLocalUserVrcPlusSupporter,
applyUserLanguage, applyUserLanguage,
applyPresenceLocation, applyPresenceLocation,
applyUserDialogLocation, applyUserDialogLocation,
setCachedUser,
syncCachedUserDisplayName,
deleteCachedUser,
clearCachedUsers,
rebuildCachedUserDisplayNameIndex,
sortUserDialogAvatars, sortUserDialogAvatars,
initUserNotes, initUserNotes,
showSendBoopDialog, showSendBoopDialog,