From c0ff062ccbc4f41a4e690d81f8dc1e1c9acf425a Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:30:20 +0900 Subject: [PATCH] wip --- packages/backend/src/core/CoreModule.ts | 12 +-- .../backend/src/core/GlobalEventService.ts | 12 +-- ...layService.ts => WorldMultiplayService.ts} | 65 ++++++-------- packages/backend/src/server/ServerModule.ts | 4 +- .../src/server/api/stream/Connection.ts | 4 +- .../channels/{world-room.ts => world.ts} | 38 ++++---- .../src/EngineBase.ts | 2 + .../frontend/src/pages/rooms/room.core.vue | 4 +- packages/frontend/src/pages/world.vue | 4 +- packages/frontend/src/world/controller.ts | 16 ---- .../src/world/engineControllerBase.ts | 31 +++++++ packages/frontend/src/world/multiplayer.ts | 14 +-- .../frontend/src/world/room/controller.ts | 28 ------ .../frontend/src/world/room/multiplayer.ts | 90 ------------------- 14 files changed, 106 insertions(+), 218 deletions(-) rename packages/backend/src/core/{WorldRoomMultiplayService.ts => WorldMultiplayService.ts} (58%) rename packages/backend/src/server/api/stream/channels/{world-room.ts => world.ts} (60%) delete mode 100644 packages/frontend/src/world/room/multiplayer.ts diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index bc59095682..bf2fa1858e 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -156,7 +156,7 @@ import { QueueService } from './QueueService.js'; import { LoggerService } from './LoggerService.js'; import { WorldRoomService } from './WorldRoomService.js'; import { WorldRoomEntityService } from './entities/WorldRoomEntityService.js'; -import { WorldRoomMultiplayService } from './WorldRoomMultiplayService.js'; +import { WorldMultiplayService } from './WorldMultiplayService.js'; import { WorldAvatarService } from './WorldAvatarService.js'; import { WorldAvatarEntityService } from './entities/WorldAvatarEntityService.js'; import { TelemetryService } from './telemetry/TelemetryService.js'; @@ -237,7 +237,7 @@ const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisti const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService }; const $PageService: Provider = { provide: 'PageService', useExisting: PageService }; const $WorldRoomService: Provider = { provide: 'WorldRoomService', useExisting: WorldRoomService }; -const $WorldRoomMultiplayService: Provider = { provide: 'WorldRoomMultiplayService', useExisting: WorldRoomMultiplayService }; +const $WorldMultiplayService: Provider = { provide: 'WorldMultiplayService', useExisting: WorldMultiplayService }; const $WorldAvatarService: Provider = { provide: 'WorldAvatarService', useExisting: WorldAvatarService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; @@ -395,7 +395,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ReversiService, PageService, WorldRoomService, - WorldRoomMultiplayService, + WorldMultiplayService, WorldAvatarService, ChartLoggerService, @@ -551,7 +551,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ReversiService, $PageService, $WorldRoomService, - $WorldRoomMultiplayService, + $WorldMultiplayService, $WorldAvatarService, $ChartLoggerService, @@ -707,7 +707,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ReversiService, PageService, WorldRoomService, - WorldRoomMultiplayService, + WorldMultiplayService, WorldAvatarService, FederationChart, @@ -861,7 +861,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ReversiService, $PageService, $WorldRoomService, - $WorldRoomMultiplayService, + $WorldMultiplayService, $WorldAvatarService, $FederationChart, diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index fd5875bf13..5874dcaa2b 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -173,7 +173,7 @@ export interface ChatEventTypes { }; } -export interface WorldRoomEventTypes { +export interface WorldEventTypes { enter: { user: Packed<'UserLite'>; avatar: Packed<'WorldAvatarLite'>['def'] | null; @@ -325,9 +325,9 @@ export type GlobalEvents = { name: `chatRoomStream:${MiChatRoom['id']}`; payload: EventTypesToEventPayload; }; - worldRoom: { - name: `worldRoomStream:${string}`; - payload: EventTypesToEventPayload; + world: { + name: `worldStream:${string}`; + payload: EventTypesToEventPayload; }; reversi: { name: `reversiStream:${MiUser['id']}`; @@ -451,7 +451,7 @@ export class GlobalEventService { } @bindThis - public publishWorldRoomStream(roomId: string, type: K, value?: WorldRoomEventTypes[K]): void { - this.publish(`worldRoomStream:${roomId}`, type, typeof value === 'undefined' ? null : value); + public publishWorldStream(spaceKey: string, type: K, value?: WorldEventTypes[K]): void { + this.publish(`worldStream:${spaceKey}`, type, typeof value === 'undefined' ? null : value); } } diff --git a/packages/backend/src/core/WorldRoomMultiplayService.ts b/packages/backend/src/core/WorldMultiplayService.ts similarity index 58% rename from packages/backend/src/core/WorldRoomMultiplayService.ts rename to packages/backend/src/core/WorldMultiplayService.ts index 35c5967275..c7d2c9d1d1 100644 --- a/packages/backend/src/core/WorldRoomMultiplayService.ts +++ b/packages/backend/src/core/WorldMultiplayService.ts @@ -7,10 +7,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In, Not } from 'typeorm'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; -import { - MiWorldRoom, -} from '@/models/_.js'; -import type { MiWorldAvatar, WorldRoomsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; @@ -30,7 +26,7 @@ type PlayerState = { }; @Injectable() -export class WorldRoomMultiplayService { +export class WorldMultiplayService { constructor( @Inject(DI.db) private db: DataSource, @@ -38,9 +34,6 @@ export class WorldRoomMultiplayService { @Inject(DI.redis) private redisClient: Redis.Redis, - @Inject(DI.worldRoomsRepository) - private worldRoomsRepository: WorldRoomsRepository, - private roleService: RoleService, private queryService: QueryService, private idService: IdService, @@ -52,74 +45,72 @@ export class WorldRoomMultiplayService { } @bindThis - public async enter(userId: MiUser['id'], roomId: MiWorldRoom['id']) { - console.log('enter', { userId, roomId }); + public async enter(userId: MiUser['id'], spaceKey: string) { + console.log('enter', { userId, spaceKey: spaceKey }); // TODO: atomicにやる - const currentPlayers = await this.redisClient.hlen(`worldRoom:${roomId}:players`); - if (currentPlayers < 10) { + const currentPlayers = await this.redisClient.hlen(`world:${spaceKey}:players`); + if (currentPlayers < 50) { const redisPipeline = this.redisClient.pipeline(); - redisPipeline.hset(`worldRoom:${roomId}:players`, userId, 1); - redisPipeline.hexpire(`worldRoom:${roomId}:players`, 30, 'FIELDS', 1, userId); + redisPipeline.hset(`world:${spaceKey}:players`, userId, 1); + redisPipeline.hexpire(`world:${spaceKey}:players`, 30, 'FIELDS', 1, userId); await redisPipeline.exec(); } else { - throw new Error('Room is full.'); + throw new Error('The group is full.'); } // TODO: 既に入っていたらスキップ const avatar = await this.worldAvatarService.getActiveAvatarOfUser(userId); - this.globalEventService.publishWorldRoomStream(roomId, 'enter', { + this.globalEventService.publishWorldStream(spaceKey, 'enter', { user: await this.userEntityService.pack(userId), avatar: avatar?.def, }); } @bindThis - public async heartbeat(userId: MiUser['id'], roomId: MiWorldRoom['id']) { - const exists = await this.redisClient.hexists(`worldRoom:${roomId}:players`, userId); + public async heartbeat(userId: MiUser['id'], spaceKey: string) { + const exists = await this.redisClient.hexists(`world:${spaceKey}:players`, userId); if (exists) { const redisPipeline = this.redisClient.pipeline(); - redisPipeline.hexpire(`worldRoom:${roomId}:players`, 30, 'FIELDS', 1, userId); - redisPipeline.hexpire(`worldRoom:${roomId}:playerStates`, 30, 'FIELDS', 1, userId); + redisPipeline.hexpire(`world:${spaceKey}:players`, 30, 'FIELDS', 1, userId); + redisPipeline.hexpire(`world:${spaceKey}:playerStates`, 30, 'FIELDS', 1, userId); await redisPipeline.exec(); } else { - throw new Error('Not in room.'); + throw new Error('Not in the group.'); } } @bindThis - public async left(userId: MiUser['id'], roomId: MiWorldRoom['id']) { - console.log('left', { userId, roomId }); - + public async left(userId: MiUser['id'], spaceKey: string) { const redisPipeline = this.redisClient.pipeline(); - redisPipeline.hdel(`worldRoom:${roomId}:players`, userId); - redisPipeline.hdel(`worldRoom:${roomId}:playerStates`, userId); + redisPipeline.hdel(`world:${spaceKey}:players`, userId); + redisPipeline.hdel(`world:${spaceKey}:playerStates`, userId); await redisPipeline.exec(); - this.globalEventService.publishWorldRoomStream(roomId, 'left', { + this.globalEventService.publishWorldStream(spaceKey, 'left', { userId, }); } @bindThis - public async updatePlayerState(userId: MiUser['id'], roomId: MiWorldRoom['id'], state: PlayerState) { + public async updatePlayerState(userId: MiUser['id'], spaceKey: string, state: PlayerState) { const redisPipeline = this.redisClient.pipeline(); - redisPipeline.hset(`worldRoom:${roomId}:playerStates`, userId, JSON.stringify(state)); - redisPipeline.hexpire(`worldRoom:${roomId}:playerStates`, 30, 'FIELDS', 1, userId); + redisPipeline.hset(`world:${spaceKey}:playerStates`, userId, JSON.stringify(state)); + redisPipeline.hexpire(`world:${spaceKey}:playerStates`, 30, 'FIELDS', 1, userId); await redisPipeline.exec(); } @bindThis - public async getPlayerStates(roomId: MiWorldRoom['id']): Promise> { - const entries = await this.redisClient.hgetall(`worldRoom:${roomId}:playerStates`); + public async getPlayerStates(spaceKey: string): Promise> { + const entries = await this.redisClient.hgetall(`world:${spaceKey}:playerStates`); return Object.fromEntries(Object.entries(entries).map(([userId, state]) => [userId, JSON.parse(state) as PlayerState])); } @bindThis - public getPlayerStatesAndHeatbeat(userId: MiUser['id'], roomId: MiWorldRoom['id']): Promise> { + public getPlayerStatesAndHeatbeat(userId: MiUser['id'], spaceKey: string): Promise> { // TODO: atomicにやる - this.heartbeat(userId, roomId); - return this.getPlayerStates(roomId); + this.heartbeat(userId, spaceKey); + return this.getPlayerStates(spaceKey); } @bindThis @@ -135,8 +126,8 @@ export class WorldRoomMultiplayService { } @bindThis - public async getPlayerProfiles(roomId: MiWorldRoom['id'], userId?: MiUser['id']): Promise> { - let playerIds = await this.redisClient.hkeys(`worldRoom:${roomId}:players`); + public async getPlayerProfiles(spaceKey: string, userId?: MiUser['id']): Promise> { + let playerIds = await this.redisClient.hkeys(`world:${spaceKey}:players`); playerIds = playerIds.filter(id => id !== userId); const packedUsers = await this.userEntityService.packMany(playerIds); diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index eb0d203e7d..beb312b1be 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -49,7 +49,7 @@ import { ChatUserChannel } from './api/stream/channels/chat-user.js'; import { ChatRoomChannel } from './api/stream/channels/chat-room.js'; import { ReversiChannel } from './api/stream/channels/reversi.js'; import { ReversiGameChannel } from './api/stream/channels/reversi-game.js'; -import { WorldRoomChannel } from './api/stream/channels/world-room.js'; +import { WorldChannel } from './api/stream/channels/world.js'; import { NoteStreamingHidingService } from './api/stream/NoteStreamingHidingService.js'; import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js'; @@ -100,7 +100,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j QueueStatsChannel, ServerStatsChannel, UserListChannel, - WorldRoomChannel, + WorldChannel, NoteStreamingHidingService, OpenApiServerService, OAuth2ProviderService, diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index 83f117176b..fe099d73ce 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -35,7 +35,7 @@ import { ChatUserChannel } from '@/server/api/stream/channels/chat-user.js'; import { ChatRoomChannel } from '@/server/api/stream/channels/chat-room.js'; import { ReversiChannel } from '@/server/api/stream/channels/reversi.js'; import { ReversiGameChannel } from '@/server/api/stream/channels/reversi-game.js'; -import { WorldRoomChannel } from '@/server/api/stream/channels/world-room.js'; +import { WorldChannel } from '@/server/api/stream/channels/world.js'; import type { ChannelRequest } from './channel.js'; import type { ChannelConstructor } from './channel.js'; import type Channel from './channel.js'; @@ -339,7 +339,7 @@ export default class Connection { case 'chatRoom': return ChatRoomChannel; case 'reversi': return ReversiChannel; case 'reversiGame': return ReversiGameChannel; - case 'worldRoom': return WorldRoomChannel; + case 'worldRoom': return WorldChannel; default: throw new Error(`no such channel: ${name}`); diff --git a/packages/backend/src/server/api/stream/channels/world-room.ts b/packages/backend/src/server/api/stream/channels/world.ts similarity index 60% rename from packages/backend/src/server/api/stream/channels/world-room.ts rename to packages/backend/src/server/api/stream/channels/world.ts index 18c8b37535..aad4aa7d6a 100644 --- a/packages/backend/src/server/api/stream/channels/world-room.ts +++ b/packages/backend/src/server/api/stream/channels/world.ts @@ -10,16 +10,17 @@ import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; import { WorldRoomService } from '@/core/WorldRoomService.js'; -import { WorldRoomMultiplayService } from '@/core/WorldRoomMultiplayService.js'; +import { WorldMultiplayService } from '@/core/WorldMultiplayService.js'; import Channel, { type ChannelRequest } from '../channel.js'; @Injectable({ scope: Scope.TRANSIENT }) -export class WorldRoomChannel extends Channel { - public readonly chName = 'worldRoom'; +export class WorldChannel extends Channel { + public readonly chName = 'world'; public static shouldShare = false; public static requireCredential = true as const; - public static kind = 'read:worldRoom'; + public static kind = 'read:world'; private roomId: string; + private spaceKey: string; private intervalId: NodeJS.Timeout; private isEntered = false; @@ -28,20 +29,17 @@ export class WorldRoomChannel extends Channel { request: ChannelRequest, private worldRoomService: WorldRoomService, - private worldRoomMultiplayService: WorldRoomMultiplayService, + private worldMultiplayService: WorldMultiplayService, ) { super(request); } @bindThis public async init(params: JsonObject): Promise { - if (typeof params.roomId !== 'string') return false; + if (typeof params.spaceKey !== 'string') return false; if (!this.user) return false; - this.roomId = params.roomId; - - const room = await this.worldRoomService.findRoomById(this.roomId); - if (room == null) return false; + this.spaceKey = params.spaceKey; try { await this.enter(); @@ -49,7 +47,7 @@ export class WorldRoomChannel extends Channel { return false; } - this.subscriber.on(`worldRoomStream:${this.roomId}`, this.onEvent); + this.subscriber.on(`worldStream:${this.spaceKey}`, this.onEvent); return true; } @@ -58,29 +56,29 @@ export class WorldRoomChannel extends Channel { private async enter() { if (this.isEntered) return; - await this.worldRoomMultiplayService.enter(this.user!.id, this.roomId); + await this.worldMultiplayService.enter(this.user!.id, this.spaceKey); this.isEntered = true; this.send('entered', { - playerProfiles: await this.worldRoomMultiplayService.getPlayerProfiles(this.roomId, this.user!.id), + playerProfiles: await this.worldMultiplayService.getPlayerProfiles(this.spaceKey, this.user!.id), }); this.intervalId = setInterval(async () => { - const states = await this.worldRoomMultiplayService.getPlayerStatesAndHeatbeat(this.user!.id, this.roomId); + const states = await this.worldMultiplayService.getPlayerStatesAndHeatbeat(this.user!.id, this.spaceKey); delete states[this.user!.id]; this.send('sync', states); }, 100); } @bindThis - private async onEvent(data: GlobalEvents['worldRoom']['payload']) { + private async onEvent(data: GlobalEvents['world']['payload']) { switch (data.type) { case 'enter': { if (data.body.user.id === this.user!.id) return; // 自分の入室は無視 this.send('playerEntered', { id: data.body.user.id, - profile: this.worldRoomMultiplayService.packPlayerProfile(data.body.user, data.body.avatar), + profile: this.worldMultiplayService.packPlayerProfile(data.body.user, data.body.avatar), }); break; } @@ -98,8 +96,8 @@ export class WorldRoomChannel extends Channel { public onMessage(type: string, body: any) { switch (type) { case 'update': - if (this.roomId && this.isEntered) { - this.worldRoomMultiplayService.updatePlayerState(this.user!.id, this.roomId, body); + if (this.spaceKey != null && this.isEntered) { + this.worldMultiplayService.updatePlayerState(this.user!.id, this.spaceKey, body); } break; } @@ -107,9 +105,9 @@ export class WorldRoomChannel extends Channel { @bindThis public dispose() { - this.subscriber.off(`worldRoomStream:${this.roomId}`, this.onEvent); + this.subscriber.off(`worldStream:${this.spaceKey}`, this.onEvent); clearInterval(this.intervalId); - this.worldRoomMultiplayService.left(this.user!.id, this.roomId); + this.worldMultiplayService.left(this.user!.id, this.spaceKey); } } diff --git a/packages/frontend-misskey-world-engine/src/EngineBase.ts b/packages/frontend-misskey-world-engine/src/EngineBase.ts index b756f4bbf7..09384ade26 100644 --- a/packages/frontend-misskey-world-engine/src/EngineBase.ts +++ b/packages/frontend-misskey-world-engine/src/EngineBase.ts @@ -7,6 +7,8 @@ import * as BABYLON from '@babylonjs/core/pure.js'; import EventEmitter from 'eventemitter3'; import { PlayerContainer, type PlayerProfile, type PlayerState } from './PlayerContainer.js'; +// TODO: multiplayer関連は全ての子クラスで必要とは限らない(preview用など)ため、このクラスを継承する別のabstract classに分離 + const IN_WEB_WORKER = typeof window === 'undefined'; export type EngineBaseEvents = { diff --git a/packages/frontend/src/pages/rooms/room.core.vue b/packages/frontend/src/pages/rooms/room.core.vue index 0f412dccd3..4864f5c2c5 100644 --- a/packages/frontend/src/pages/rooms/room.core.vue +++ b/packages/frontend/src/pages/rooms/room.core.vue @@ -189,7 +189,7 @@ import { prefer } from '@/preferences.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { miLocalStorage } from '@/local-storage.js'; import { FURNITURE_UI_DEFS } from '@/world/room/furniture-ui-defs.js'; -import { RoomMultiplayer } from '@/world/room/multiplayer.js'; +import { Multiplayer } from '@/world/multiplayer.js'; import { $i } from '@/i.js'; import { userPage } from '@/filters/user.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; @@ -340,7 +340,7 @@ const roomControllerOptions = computed(() => ({ })); const controller = markRaw(new RoomController(deepClone(initialRoomState), roomControllerOptions.value)); -const multiplayer = markRaw(new RoomMultiplayer(props.room.id, controller)); +const multiplayer = markRaw(new Multiplayer(`room:${props.room.id}`, controller)); watch(controller.roomState, () => { controller.roomState.value.worldScale = WORLD_SCALE; diff --git a/packages/frontend/src/pages/world.vue b/packages/frontend/src/pages/world.vue index b2d7046016..83850fe071 100644 --- a/packages/frontend/src/pages/world.vue +++ b/packages/frontend/src/pages/world.vue @@ -85,7 +85,7 @@ import { prefer } from '@/preferences.js'; import { isTouchUsing } from '@/utility/touch.js'; import { deviceKind } from '@/utility/device-kind.js'; import MkProgressBar from '@/components/MkProgressBar.vue'; -import { WorldMultiplayer } from '@/world/multiplayer.js'; +import { Multiplayer } from '@/world/multiplayer.js'; import { $i } from '@/i.js'; import { userPage } from '@/filters/user.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; @@ -132,7 +132,7 @@ const worldControllerOptions = computed(() => ({ })); const controller = markRaw(new WorldEngineController(worldControllerOptions.value)); -const multiplayer = markRaw(new WorldMultiplayer('0', controller)); +const multiplayer = markRaw(new Multiplayer('world:main:0', controller)); const pointedPlayerInfo = ref(null); const isMenuShowing = ref(!isNarrow); diff --git a/packages/frontend/src/world/controller.ts b/packages/frontend/src/world/controller.ts index 00dc6121d8..f453293d5a 100644 --- a/packages/frontend/src/world/controller.ts +++ b/packages/frontend/src/world/controller.ts @@ -70,20 +70,4 @@ export class WorldEngineController extends EngineControllerBase { public setCameraJoystickMoveVector(vec: { x: number; y: number }) { this.call('cameraJoystickMove', [vec]); } - - public updatePlayerProfiles(profiles: Record) { - this.call('updatePlayerProfiles', [profiles]); - } - - public updatePlayerStates(states: Record) { - this.call('updatePlayerStates', [states]); - } - - public clearPlayers() { - this.call('clearPlayers'); - } - - public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) { - this.call('updateAvatarDisplayOptions', [options]); - } } diff --git a/packages/frontend/src/world/engineControllerBase.ts b/packages/frontend/src/world/engineControllerBase.ts index 514c431a8a..bd3a30d8ad 100644 --- a/packages/frontend/src/world/engineControllerBase.ts +++ b/packages/frontend/src/world/engineControllerBase.ts @@ -6,9 +6,12 @@ import { reactive, ref, shallowRef, triggerRef, watch } from 'vue'; import { EventEmitter } from 'eventemitter3'; import type { EngineBase, EngineBaseEvents } from 'frontend-misskey-world-engine/src/EngineBase.js'; +import type { PlayerProfile, PlayerState } from 'misskey-world-engine/src/PlayerContainer.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; +// TODO: multiplayer関連は全ての子クラスで必要とは限らない(preview用など)ため、このクラスを継承する別のabstract classに分離 + export type EngineControllerBaseOptions = { workerMode?: boolean; graphicsQuality: number; @@ -414,6 +417,34 @@ export abstract class EngineControllerBase) { + this.call('updatePlayerProfiles', [profiles]); + } + + public updatePlayerStates(states: Record) { + this.call('updatePlayerStates', [states]); + } + + public clearPlayers() { + this.call('clearPlayers'); + } + + public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) { + this.call('updateAvatarDisplayOptions', [options]); + } + public takeScreenshot() { return this.callAndWaitReturn('takeScreenshot'); } diff --git a/packages/frontend/src/world/multiplayer.ts b/packages/frontend/src/world/multiplayer.ts index 9c2c9d52eb..8fd49f6c22 100644 --- a/packages/frontend/src/world/multiplayer.ts +++ b/packages/frontend/src/world/multiplayer.ts @@ -7,21 +7,21 @@ import { reactive, ref, shallowRef, triggerRef, watch } from 'vue'; import { EventEmitter } from 'eventemitter3'; import * as Misskey from 'misskey-js'; import type { PlayerProfile, PlayerState } from 'misskey-world-engine/src/PlayerContainer.js'; -import type { WorldEngineController } from './controller.js'; +import type { EngineControllerBase } from './EngineControllerBase.js'; import { useStream } from '@/stream.js'; import * as os from '@/os.js'; import { withTimeout } from '@/utility/promise-timeout.js'; import { deepEqual } from '@/utility/deep-equal.js'; -export class WorldMultiplayer { +export class Multiplayer { public isOnline = ref(false); - private controller: WorldEngineController; + private controller: EngineControllerBase; private connection: Misskey.IChannelConnection | null = null; - private dimensionId: string; + private spaceKey: string; public playerProfiles: Record = {}; - constructor(dimensionId: string, controller: WorldEngineController) { - this.dimensionId = dimensionId; + constructor(spaceKey: string, controller: EngineControllerBase) { + this.spaceKey = spaceKey; this.controller = controller; this.onSync = this.onSync.bind(this); @@ -32,7 +32,7 @@ export class WorldMultiplayer { public enter() { const p = new Promise((resolve, reject) => { this.connection = useStream().useChannel('world', { - roomId: this.dimensionId, + spaceKey: this.spaceKey, }); this.connection.once('entered', ({ playerProfiles }) => { console.log('entered', playerProfiles); diff --git a/packages/frontend/src/world/room/controller.ts b/packages/frontend/src/world/room/controller.ts index 3308ed07a2..df96d48064 100644 --- a/packages/frontend/src/world/room/controller.ts +++ b/packages/frontend/src/world/room/controller.ts @@ -223,32 +223,4 @@ export class RoomController extends EngineControllerBase) { - this.call('updatePlayerProfiles', [profiles]); - } - - public updatePlayerStates(states: Record) { - this.call('updatePlayerStates', [states]); - } - - public clearPlayers() { - this.call('clearPlayers'); - } - - public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) { - this.call('updateAvatarDisplayOptions', [options]); - } } diff --git a/packages/frontend/src/world/room/multiplayer.ts b/packages/frontend/src/world/room/multiplayer.ts deleted file mode 100644 index 810b737b63..0000000000 --- a/packages/frontend/src/world/room/multiplayer.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { reactive, ref, shallowRef, triggerRef, watch } from 'vue'; -import { EventEmitter } from 'eventemitter3'; -import * as Misskey from 'misskey-js'; -import type { PlayerProfile, PlayerState } from 'misskey-world-engine/src/PlayerContainer.js'; -import type { RoomController } from './controller.js'; -import { useStream } from '@/stream.js'; -import * as os from '@/os.js'; -import { withTimeout } from '@/utility/promise-timeout.js'; -import { deepEqual } from '@/utility/deep-equal.js'; - -export class RoomMultiplayer { - public isOnline = ref(false); - private controller: RoomController; - private connection: Misskey.IChannelConnection | null = null; - private roomId: string; - public playerProfiles: Record = {}; - - constructor(roomId: string, controller: RoomController) { - this.roomId = roomId; - this.controller = controller; - - this.onSync = this.onSync.bind(this); - this.onPlayerEntered = this.onPlayerEntered.bind(this); - this.onPlayerLeft = this.onPlayerLeft.bind(this); - } - - public enter() { - const p = new Promise((resolve, reject) => { - this.connection = useStream().useChannel('worldRoom', { - roomId: this.roomId, - }); - this.connection.once('entered', ({ playerProfiles }) => { - console.log('entered', playerProfiles); - this.playerProfiles = playerProfiles; - this.controller.updatePlayerProfiles(this.playerProfiles); - this.connection!.on('sync', this.onSync); - this.connection!.on('playerEntered', this.onPlayerEntered); - this.connection!.on('playerLeft', this.onPlayerLeft); - this.isOnline.value = true; - resolve(); - }); - }); - - return withTimeout(p, 5000).catch((err) => { - this.connection?.dispose(); - this.connection = null; - throw err; - }); - } - - public left() { - if (this.connection == null) return; - this.connection.dispose(); - this.connection = null; - this.isOnline.value = false; - } - - private prevState: PlayerState | null = null; - - public updateState(state: PlayerState) { - if (this.connection == null || !this.isOnline.value) return; - if (this.prevState != null && deepEqual(this.prevState, state)) return; - - this.connection.send('update', state); - this.prevState = state; - } - - private onSync(states: Record) { - this.controller.updatePlayerStates(states); - } - - private onPlayerEntered(data: { id: string; profile: PlayerProfile; }) { - this.playerProfiles[data.id] = data.profile; - this.controller.updatePlayerProfiles(this.playerProfiles); - } - - private onPlayerLeft(data: { id: string; }) { - delete this.playerProfiles[data.id]; - this.controller.updatePlayerProfiles(this.playerProfiles); - } - - public dispose() { - this.left(); - } -}