Room management

Фреймворк Fresh на JavaScript позволяет создавать высокопроизводительные веб-приложения с акцентом на серверный рендеринг и реактивное обновление интерфейса. Управление комнатами (Room Management) — ключевой компонент для приложений с функционалом чатов, совместной работы и игровых платформ, где требуется синхронизация состояния между множеством пользователей.

Комната в контексте Fresh представляет собой логическую единицу, в которой объединяются пользователи и контент. Основные задачи модуля управления комнатами включают:

  • Создание и удаление комнат.
  • Управление участниками (join/leave).
  • Синхронизация состояния между клиентами.
  • Рассылка сообщений или событий внутри комнаты.

Создание комнаты

Комнаты создаются на серверной стороне, используя объектную структуру для хранения состояния. Простейшая реализация может выглядеть следующим образом:

class Room {
  constructor(id) {
    this.id = id;
    this.clients = new Set();
    this.state = {};
  }

  addClient(client) {
    this.clients.add(client);
    client.send(JSON.stringify({ type: 'room_joined', roomId: this.id }));
  }

  removeClient(client) {
    this.clients.delete(client);
    client.send(JSON.stringify({ type: 'room_left', roomId: this.id }));
  }
}

Ключевым моментом является использование Set для хранения клиентов, что обеспечивает уникальность подключений и эффективное управление памятью.

Создание новой комнаты выполняется через менеджер комнат:

class RoomManager {
  constructor() {
    this.rooms = new Map();
  }

  createRoom(id) {
    if (!this.rooms.has(id)) {
      const room = new Room(id);
      this.rooms.set(id, room);
    }
    return this.rooms.get(id);
  }

  deleteRoom(id) {
    this.rooms.delete(id);
  }

  getRoom(id) {
    return this.rooms.get(id);
  }
}

Управление участниками

Участники подключаются к комнате через WebSocket или аналогичный двунаправленный канал. Добавление и удаление клиентов должно сопровождаться синхронизацией состояния:

function broadcastToRoom(room, message) {
  room.clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify(message));
    }
  });
}

Использование функции broadcastToRoom позволяет централизованно рассылать события, например обновления чата или состояния игры.


Синхронизация состояния

Состояние комнаты может включать любую информацию: позиции игроков, список сообщений, настройки. Для минимизации нагрузки на сеть рекомендуется использовать диффы — только изменения состояния отправляются клиентам.

class Room {
  constructor(id) {
    this.id = id;
    this.clients = new Set();
    this.state = {};
  }

  updateState(newState) {
    const diff = {};
    for (const key in newState) {
      if (this.state[key] !== newState[key]) {
        diff[key] = newState[key];
      }
    }
    Object.assign(this.state, diff);
    this.broadcast(diff);
  }

  broadcast(diff) {
    this.clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ type: 'state_update', diff }));
      }
    });
  }
}

Такой подход позволяет поддерживать согласованное состояние у всех участников комнаты без полной пересылки всех данных при каждом обновлении.


Устойчивость к ошибкам и отключениям

Fresh не содержит встроенной системы управления комнатами на уровне WebSocket, поэтому важно предусматривать обработку отключений и повторных подключений:

  • Установка таймаута для проверки активности клиента.
  • Автоматическое удаление пустых комнат.
  • Восстановление состояния при повторном подключении клиента.

Пример таймаута:

class Room {
  constructor(id) {
    this.id = id;
    this.clients = new Map(); // client -> lastPing
    this.state = {};
  }

  addClient(client) {
    this.clients.set(client, Date.now());
  }

  removeInactiveClients(timeout) {
    const now = Date.now();
    for (const [client, lastPing] of this.clients) {
      if (now - lastPing > timeout) {
        this.clients.delete(client);
      }
    }
  }
}

Масштабирование и многопоточность

Для приложений с большим количеством комнат необходимо учитывать:

  • Распределение комнат по нескольким инстансам сервера.
  • Использование внешнего хранилища для состояния (Redis, PostgreSQL) с публикацией событий через pub/sub.
  • Ограничение количества участников в комнате для предотвращения перегрузки.

Пример интеграции с Redis Pub/Sub:

import { createClient } from 'redis';
const redis = createClient();

async function publishState(roomId, diff) {
  await redis.publish(`room:${roomId}`, JSON.stringify(diff));
}

async function subscribeRoom(roomId, callback) {
  const subscriber = redis.duplicate();
  await subscriber.connect();
  await subscriber.subscribe(`room:${roomId}`, message => {
    callback(JSON.parse(message));
  });
}

Это позволяет синхронизировать состояние между несколькими инстансами Fresh и обеспечивать масштабируемость без потери данных.


Оптимизация передачи данных

  • Сжатие JSON-пакетов перед отправкой клиентам.
  • Использование бинарных протоколов (MessagePack, Protobuf) для критически чувствительных к производительности приложений.
  • Локальные фильтры событий: отправка каждому клиенту только релевантных изменений.

Итоговая структура

Менеджер комнат, сами комнаты и клиенты образуют иерархическую структуру:

RoomManager
 ├─ Room 1
 │   ├─ Client A
 │   ├─ Client B
 │   └─ state
 ├─ Room 2
 │   ├─ Client C
 │   └─ state
 └─ Room N

Эта архитектура обеспечивает гибкость, масштабируемость и высокую производительность при реализации функционала чатов, совместных игр и других приложений с динамическим взаимодействием пользователей.