Room Lifecycle and States
Standard Rooms follow a well-defined lifecycle with distinct states. Understanding these states and transitions helps you create robust multiplayer experiences and handle edge cases effectively.
Room States
Standard Rooms progress through these states:
| State | Value | Description | Typical Duration |
|---|---|---|---|
AUTO_MATCHING | 1 | Room is in matchmaking pool | 0-60 seconds |
CONNECTING | 2 | Players are connecting to each other | 5-15 seconds |
ACTIVE | 3 | Room is fully connected and ready for gameplay | Until game ends |
State Transitions
Room Created → AUTO_MATCHING → CONNECTING → ACTIVE → Room Ended
↓ ↓ ↓ ↓
Private Room Players Found All Connected Game Complete
(Optional) (Auto-match) (P2P Setup) (Leave/Finish)
Lifecycle Phases
1. Room Creation Phase
The room is created and enters the initial state:
- JavaScript
- TypeScript
// Room creation triggers onRoomCreated callback
onRoomCreated: (status, room) => {
if (status) {
console.log('Room created with state:', room.status);
switch (room.status) {
case 1: // AUTO_MATCHING
console.log('Room is in auto-matching pool');
showAutoMatchScreen(room);
startMatchmakingTimer(room.autoMatchWaitSeconds);
break;
case 2: // CONNECTING
console.log('Room is in connecting phase');
showConnectingScreen();
break;
case 3: // ACTIVE
console.log('Room is already active');
showActiveGameScreen(room);
break;
}
// Store initial room state
currentRoomState = room.status;
previousRoomState = null;
}
}
import type { Room, RoomStatus } from '@veniso/moitribe-js';
// Room creation triggers onRoomCreated callback
onRoomCreated: (status: boolean, room: Room) => {
if (status) {
console.log('Room created with state:', room.status);
switch (room.status) {
case RoomStatus.AUTO_MATCHING:
console.log('Room is in auto-matching pool');
showAutoMatchScreen(room);
startMatchmakingTimer(room.autoMatchWaitSeconds);
break;
case RoomStatus.CONNECTING:
console.log('Room is in connecting phase');
showConnectingScreen();
break;
case RoomStatus.ACTIVE:
console.log('Room is already active');
showActiveGameScreen(room);
break;
}
// Store initial room state
currentRoomState = room.status;
previousRoomState = null;
}
}
2. Auto-Matching Phase
Room waits for players to join via auto-matching:
- JavaScript
- TypeScript
// Handle auto-matching state
function handleAutoMatchingState(room) {
console.log('Handling auto-matching state');
// Show matchmaking UI
showMatchmakingUI({
waitTime: room.autoMatchWaitSeconds,
currentPlayers: room.participants.length,
minPlayers: room.min_automatch_players,
maxPlayers: room.max_automatch_players
});
// Start matchmaking timer
startMatchmakingTimer(room.autoMatchWaitSeconds, () => {
console.log('Matchmaking timeout - extending search');
extendMatchmakingSearch();
});
// Monitor player joins
room.participants.forEach(participant => {
console.log('Player in matchmaking:', participant.name);
});
// Update UI as players join
updateMatchmakingProgress(room.participants.length, room.min_automatch_players);
}
// Called when onPeerJoined fires during auto-matching
onPeerJoined: (room, participantList) => {
if (room.status === 1) { // Still auto-matching
console.log('New player joined during matchmaking');
// Update progress
updateMatchmakingProgress(participantList.length, room.min_automatch_players);
// Check if ready to transition
if (participantList.length >= room.min_automatch_players) {
console.log('Minimum players reached - preparing to connect');
showTransitionToConnecting();
}
}
}
import type { Room, RoomStatus } from '@veniso/moitribe-js';
// Handle auto-matching state
function handleAutoMatchingState(room: Room): void {
console.log('Handling auto-matching state');
// Show matchmaking UI
showMatchmakingUI({
waitTime: room.autoMatchWaitSeconds,
currentPlayers: room.participants.length,
minPlayers: room.min_automatch_players,
maxPlayers: room.max_automatch_players
});
// Start matchmaking timer
startMatchmakingTimer(room.autoMatchWaitSeconds, () => {
console.log('Matchmaking timeout - extending search');
extendMatchmakingSearch();
});
// Monitor player joins
room.participants.forEach(participant => {
console.log('Player in matchmaking:', participant.name);
});
// Update UI as players join
updateMatchmakingProgress(room.participants.length, room.min_automatch_players);
}
// Called when onPeerJoined fires during auto-matching
onPeerJoined: (room: Room, participantList: string[]) => {
if (room.status === RoomStatus.AUTO_MATCHING) {
console.log('New player joined during matchmaking');
// Update progress
updateMatchmakingProgress(participantList.length, room.min_automatch_players);
// Check if ready to transition
if (participantList.length >= room.min_automatch_players) {
console.log('Minimum players reached - preparing to connect');
showTransitionToConnecting();
}
}
}
3. Connecting Phase
Players establish peer-to-peer connections:
- JavaScript
- TypeScript
// Handle connecting state
function handleConnectingState(room) {
console.log('Handling connecting state');
// Show connecting UI
showConnectingUI({
totalPlayers: room.participants.length,
connectedPlayers: room.participants.filter(p => p.isConnected).length
});
// Start connection timeout
startConnectionTimer(30, () => {
console.error('Connection timeout');
handleConnectionTimeout();
});
// Monitor connection progress
monitorConnectionProgress(room);
// Show connection tips
showConnectionTips();
}
// Monitor individual player connections
function monitorConnectionProgress(room) {
room.participants.forEach(participant => {
if (participant.isConnected) {
console.log(`✅ ${participant.name} is connected`);
} else {
console.log(`⏳ ${participant.name} is connecting...`);
}
});
// Check if all players are connected
const allConnected = room.participants.every(p => p.isConnected);
if (allConnected) {
console.log('All players connected - ready for active state');
prepareForActiveState();
}
}
// Handle connection state changes
onPeerConnected: (room, participantList) => {
if (room.status === 2) { // Still connecting
console.log('Player connected during connecting phase');
// Update connection progress
updateConnectionProgress(
participantList.length,
room.participants.length
);
// Check if ready to transition to active
if (participantList.length === room.participants.length) {
console.log('All players connected - transitioning to active');
showTransitionToActive();
}
}
}
import type { Room, RoomStatus, Participant } from '@veniso/moitribe-js';
// Handle connecting state
function handleConnectingState(room: Room): void {
console.log('Handling connecting state');
// Show connecting UI
showConnectingUI({
totalPlayers: room.participants.length,
connectedPlayers: room.participants.filter(p => p.isConnected).length
});
// Start connection timeout
startConnectionTimer(30, () => {
console.error('Connection timeout');
handleConnectionTimeout();
});
// Monitor connection progress
monitorConnectionProgress(room);
// Show connection tips
showConnectionTips();
}
// Monitor individual player connections
function monitorConnectionProgress(room: Room): void {
room.participants.forEach(participant => {
if (participant.isConnected) {
console.log(`✅ ${participant.name} is connected`);
} else {
console.log(`⏳ ${participant.name} is connecting...`);
}
});
// Check if all players are connected
const allConnected = room.participants.every(p => p.isConnected);
if (allConnected) {
console.log('All players connected - ready for active state');
prepareForActiveState();
}
}
// Handle connection state changes
onPeerConnected: (room: Room, participantList: string[]) => {
if (room.status === RoomStatus.CONNECTING) {
console.log('Player connected during connecting phase');
// Update connection progress
updateConnectionProgress(
participantList.length,
room.participants.length
);
// Check if ready to transition to active
if (participantList.length === room.participants.length) {
console.log('All players connected - transitioning to active');
showTransitionToActive();
}
}
}
4. Active Phase
Room is fully connected and ready for gameplay:
- JavaScript
- TypeScript
// Handle active state
function handleActiveState(room) {
console.log('Handling active state - game ready!');
// Hide all transition screens
hideAllTransitionScreens();
// Show game UI
showGameUI(room);
// Enable game controls
enableGameControls();
// Start game loop
startGameLoop();
// Begin game state synchronization
startGameStateSync();
// Show game start notification
showGameStartNotification();
// Log game start for analytics
logGameStart(room);
// Start game timer
startGameTimer();
// Enable voice chat if available
if (isVoiceChatAvailable()) {
enableVoiceChat();
}
}
// Called when onRoomConnected fires
onRoomConnected: (status, room) => {
if (status) {
console.log('Room connected - entering active state');
// Verify room is in active state
if (room.status === 3) {
handleActiveState(room);
} else {
console.warn('Room connected but not in active state:', room.status);
// Handle unexpected state
handleUnexpectedRoomState(room);
}
} else {
console.error('Room connection failed');
handleConnectionFailure();
}
}
import type { Room, RoomStatus } from '@veniso/moitribe-js';
// Handle active state
function handleActiveState(room: Room): void {
console.log('Handling active state - game ready!');
// Hide all transition screens
hideAllTransitionScreens();
// Show game UI
showGameUI(room);
// Enable game controls
enableGameControls();
// Start game loop
startGameLoop();
// Begin game state synchronization
startGameStateSync();
// Show game start notification
showGameStartNotification();
// Log game start for analytics
logGameStart(room);
// Start game timer
startGameTimer();
// Enable voice chat if available
if (isVoiceChatAvailable()) {
enableVoiceChat();
}
}
// Called when onRoomConnected fires
onRoomConnected: (status: boolean, room: Room) => {
if (status) {
console.log('Room connected - entering active state');
// Verify room is in active state
if (room.status === RoomStatus.ACTIVE) {
handleActiveState(room);
} else {
console.warn('Room connected but not in active state:', room.status);
// Handle unexpected state
handleUnexpectedRoomState(room);
}
} else {
console.error('Room connection failed');
handleConnectionFailure();
}
}
State Management
Track State Changes
- JavaScript
- TypeScript
// State tracking variables
let currentRoomState = null;
let previousRoomState = null;
let stateHistory = [];
// Monitor state changes
function monitorRoomState(room) {
const newState = room.status;
if (newState !== currentRoomState) {
console.log(`Room state transition: ${currentRoomState} → ${newState}`);
// Record state change
previousRoomState = currentRoomState;
currentRoomState = newState;
stateHistory.push({
from: previousRoomState,
to: newState,
timestamp: Date.now()
});
// Handle state transition
handleStateTransition(previousRoomState, newState, room);
}
}
// Handle specific transitions
function handleStateTransition(fromState, toState, room) {
console.log(`Handling transition: ${fromState} → ${toState}`);
// Auto-matching → Connecting
if (fromState === 1 && toState === 2) {
console.log('Players found - establishing connections');
showTransitionScreen('Connecting Players...');
startConnectionPhase();
}
// Connecting → Active
else if (fromState === 2 && toState === 3) {
console.log('All connected - starting game');
showTransitionScreen('Starting Game...');
prepareGameStart();
}
// Any → Auto-matching (re-matching)
else if (toState === 1 && fromState !== null) {
console.log('Returning to auto-matching');
showTransitionScreen('Searching for Players...');
resetMatchmaking();
}
}
// Call this in all room callbacks
function updateRoomState(room) {
monitorRoomState(room);
updateUIForCurrentState(room);
}
import type { Room, RoomStatus } from '@veniso/moitribe-js';
// State tracking variables
let currentRoomState: RoomStatus | null = null;
let previousRoomState: RoomStatus | null = null;
let stateHistory: Array<{
from: RoomStatus | null;
to: RoomStatus;
timestamp: number;
}> = [];
// Monitor state changes
function monitorRoomState(room: Room): void {
const newState = room.status;
if (newState !== currentRoomState) {
console.log(`Room state transition: ${currentRoomState} → ${newState}`);
// Record state change
previousRoomState = currentRoomState;
currentRoomState = newState;
stateHistory.push({
from: previousRoomState,
to: newState,
timestamp: Date.now()
});
// Handle state transition
handleStateTransition(previousRoomState, newState, room);
}
}
// Handle specific transitions
function handleStateTransition(
fromState: RoomStatus | null,
toState: RoomStatus,
room: Room
): void {
console.log(`Handling transition: ${fromState} → ${toState}`);
// Auto-matching → Connecting
if (fromState === RoomStatus.AUTO_MATCHING && toState === RoomStatus.CONNECTING) {
console.log('Players found - establishing connections');
showTransitionScreen('Connecting Players...');
startConnectionPhase();
}
// Connecting → Active
else if (fromState === RoomStatus.CONNECTING && toState === RoomStatus.ACTIVE) {
console.log('All connected - starting game');
showTransitionScreen('Starting Game...');
prepareGameStart();
}
// Any → Auto-matching (re-matching)
else if (toState === RoomStatus.AUTO_MATCHING && fromState !== null) {
console.log('Returning to auto-matching');
showTransitionScreen('Searching for Players...');
resetMatchmaking();
}
}
// Call this in all room callbacks
function updateRoomState(room: Room): void {
monitorRoomState(room);
updateUIForCurrentState(room);
}
State-Based UI Updates
- JavaScript
- TypeScript
// Update UI based on current room state
function updateUIForCurrentState(room) {
switch (room.status) {
case 1: // AUTO_MATCHING
showMatchmakingUI();
hideGameUI();
hideConnectingUI();
break;
case 2: // CONNECTING
showConnectingUI();
hideMatchmakingUI();
hideGameUI();
break;
case 3: // ACTIVE
showGameUI();
hideMatchmakingUI();
hideConnectingUI();
break;
default:
console.warn('Unknown room state:', room.status);
showErrorUI('Unknown room state');
}
// Update common elements
updatePlayerCount(room.participants.length);
updateRoomID(room.roomID);
updateConnectionStatus(room.status);
}
// State-specific UI components
function showMatchmakingUI() {
document.getElementById('matchmaking-screen').style.display = 'block';
document.getElementById('waiting-text').textContent = 'Searching for players...';
startMatchmakingAnimation();
}
function showConnectingUI() {
document.getElementById('connecting-screen').style.display = 'block';
document.getElementById('connecting-text').textContent = 'Establishing connections...';
startConnectingAnimation();
}
function showGameUI() {
document.getElementById('game-screen').style.display = 'block';
document.getElementById('game-hud').style.display = 'block';
initializeGameHUD();
}
import type { Room, RoomStatus } from '@veniso/moitribe-js';
// Update UI based on current room state
function updateUIForCurrentState(room: Room): void {
switch (room.status) {
case RoomStatus.AUTO_MATCHING:
showMatchmakingUI();
hideGameUI();
hideConnectingUI();
break;
case RoomStatus.CONNECTING:
showConnectingUI();
hideMatchmakingUI();
hideGameUI();
break;
case RoomStatus.ACTIVE:
showGameUI();
hideMatchmakingUI();
hideConnectingUI();
break;
default:
console.warn('Unknown room state:', room.status);
showErrorUI('Unknown room state');
}
// Update common elements
updatePlayerCount(room.participants.length);
updateRoomID(room.roomID);
updateConnectionStatus(room.status);
}
// State-specific UI components
function showMatchmakingUI(): void {
const matchmakingScreen = document.getElementById('matchmaking-screen');
if (matchmakingScreen) {
matchmakingScreen.style.display = 'block';
}
const waitingText = document.getElementById('waiting-text');
if (waitingText) {
waitingText.textContent = 'Searching for players...';
}
startMatchmakingAnimation();
}
function showConnectingUI(): void {
const connectingScreen = document.getElementById('connecting-screen');
if (connectingScreen) {
connectingScreen.style.display = 'block';
}
const connectingText = document.getElementById('connecting-text');
if (connectingText) {
connectingText.textContent = 'Establishing connections...';
}
startConnectingAnimation();
}
function showGameUI(): void {
const gameScreen = document.getElementById('game-screen');
if (gameScreen) {
gameScreen.style.display = 'block';
}
const gameHUD = document.getElementById('game-hud');
if (gameHUD) {
gameHUD.style.display = 'block';
}
initializeGameHUD();
}
Error States and Recovery
Handle State Transitions Failures
- JavaScript
- TypeScript
// Handle failed state transitions
function handleStateTransitionFailure(fromState, toState, error) {
console.error(`State transition failed: ${fromState} → ${toState}`, error);
// Show error to user
showStateTransitionError(fromState, toState, error);
// Attempt recovery based on current state
switch (fromState) {
case 1: // Failed during auto-matching
console.log('Auto-matching failed - retrying');
retryAutoMatching();
break;
case 2: // Failed during connecting
console.log('Connection failed - attempting reconnection');
attemptReconnection();
break;
case 3: // Failed during active game
console.log('Active game failed - attempting recovery');
attemptGameRecovery();
break;
}
}
// Recovery strategies
function retryAutoMatching() {
showRetryScreen('Retrying matchmaking...');
setTimeout(() => {
// Restart matchmaking with expanded criteria
restartMatchmaking({
expandSearch: true,
increaseWaitTime: true
});
}, 2000);
}
function attemptReconnection() {
showReconnectionScreen('Attempting to reconnect...');
let attempts = 0;
const maxAttempts = 5;
const reconnectInterval = setInterval(() => {
attempts++;
updateReconnectionProgress(attempts, maxAttempts);
if (attempts >= maxAttempts) {
clearInterval(reconnectInterval);
handleReconnectionFailure();
return;
}
// Attempt reconnection
attemptRoomReconnect();
}, 3000);
}
import type { RoomStatus } from '@veniso/moitribe-js';
// Handle failed state transitions
function handleStateTransitionFailure(
fromState: RoomStatus,
toState: RoomStatus,
error: Error
): void {
console.error(`State transition failed: ${fromState} → ${toState}`, error);
// Show error to user
showStateTransitionError(fromState, toState, error);
// Attempt recovery based on current state
switch (fromState) {
case RoomStatus.AUTO_MATCHING:
console.log('Auto-matching failed - retrying');
retryAutoMatching();
break;
case RoomStatus.CONNECTING:
console.log('Connection failed - attempting reconnection');
attemptReconnection();
break;
case RoomStatus.ACTIVE:
console.log('Active game failed - attempting recovery');
attemptGameRecovery();
break;
}
}
// Recovery strategies
function retryAutoMatching(): void {
showRetryScreen('Retrying matchmaking...');
setTimeout(() => {
// Restart matchmaking with expanded criteria
restartMatchmaking({
expandSearch: true,
increaseWaitTime: true
});
}, 2000);
}
function attemptReconnection(): void {
showReconnectionScreen('Attempting to reconnect...');
let attempts = 0;
const maxAttempts = 5;
const reconnectInterval = setInterval(() => {
attempts++;
updateReconnectionProgress(attempts, maxAttempts);
if (attempts >= maxAttempts) {
clearInterval(reconnectInterval);
handleReconnectionFailure();
return;
}
// Attempt reconnection
attemptRoomReconnect();
}, 3000);
}
Best Practices
State Management
- Track state changes systematically
- Log state transitions for debugging
- Handle unexpected states gracefully
- Implement proper state recovery
User Experience
- Show clear feedback during transitions
- Use appropriate loading animations
- Provide progress indicators
- Handle timeouts gracefully
Error Handling
- Implement retry mechanisms
- Provide fallback options
- Log errors for debugging
- Maintain data consistency
Performance
- Minimize UI updates during transitions
- Use efficient state tracking
- Avoid blocking operations
- Clean up resources properly