Mostly working websocket stuff, some message weirdness at the moment...
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:backend/game_room_manager.dart';
|
||||
import 'package:dart_frog_web_socket/dart_frog_web_socket.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_models/room.dart';
|
||||
|
||||
final _logger = Logger('SocketManager');
|
||||
|
||||
class SocketManager {
|
||||
// Default constructor returns instance
|
||||
factory SocketManager() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Private constructor
|
||||
SocketManager._();
|
||||
|
||||
// Singleton instance
|
||||
static final SocketManager instance = SocketManager._();
|
||||
|
||||
// Store connections: GameRoomUuid -> UserUuid -> WebSocketChannel
|
||||
final _connections = <String, Map<String, WebSocketChannel>>{};
|
||||
// Store isolate port and stream subscription to said port
|
||||
final _gameRoomSendPorts = <String, SendPort>{};
|
||||
final _gameRoomSpSubs = <String, StreamSubscription<dynamic>>{};
|
||||
|
||||
// Add a new connection
|
||||
Future<Status> addConnection(
|
||||
WebSocketChannel connection, {
|
||||
required String roomUuid,
|
||||
required String userUuid,
|
||||
}) async {
|
||||
_logger.finer('Adding connection to socket manager for user $userUuid in room $roomUuid');
|
||||
if (!_gameRoomSpSubs.containsKey(roomUuid)) {
|
||||
final status = await spawnGameRoomIsolate(roomUuid);
|
||||
if (status == Status.failure) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
_connections.putIfAbsent(roomUuid, () => <String, WebSocketChannel>{});
|
||||
_connections[roomUuid]![userUuid] = connection;
|
||||
return Status.success;
|
||||
}
|
||||
|
||||
// Remove a connection
|
||||
void removeConnection(String roomUuid, String userUuid) {
|
||||
_connections[roomUuid]?.remove(userUuid);
|
||||
if (_connections[roomUuid]?.isEmpty ?? false) {
|
||||
_connections.remove(roomUuid);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a specific user's connection
|
||||
WebSocketChannel? getConnection(String roomUuid, String userUuid) {
|
||||
return _connections[roomUuid]?[userUuid];
|
||||
}
|
||||
|
||||
// Get all connections in a room
|
||||
Map<String, WebSocketChannel>? getRoomConnections(String roomUuid) {
|
||||
return _connections[roomUuid];
|
||||
}
|
||||
|
||||
// Broadcast message to all users in a room except sender
|
||||
void broadcastToRoom(String roomUuid, GameRoomMessage message, {String? excludeUserUuid}) {
|
||||
_logger.fine('Broadcasting ${message.type} to room $roomUuid');
|
||||
_connections[roomUuid]?.forEach((userUuid, connection) {
|
||||
if (userUuid != excludeUserUuid) {
|
||||
connection.sink.add(jsonEncode(message.toJson()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if a user is connected
|
||||
bool isConnected(String roomUuid, String userUuid) {
|
||||
return _connections[roomUuid]?.containsKey(userUuid) ?? false;
|
||||
}
|
||||
|
||||
Future<Status> spawnGameRoomIsolate(String roomUuid) async {
|
||||
try {
|
||||
_logger.finest('Spawning isolate for room $roomUuid');
|
||||
if (_gameRoomSendPorts.containsKey(roomUuid)) {
|
||||
_logger.severe('Tried to create a sendPort for an existing room uuid: $roomUuid', null, StackTrace.current);
|
||||
throw Exception('Cannot create sendPort, room already has one');
|
||||
}
|
||||
final receivePort = ReceivePort();
|
||||
final sp = receivePort.sendPort;
|
||||
await Isolate.spawn(LiveGameRoom.spawn, LiveGameRoomData(roomUuid: roomUuid, wsSendPort: sp));
|
||||
// used to get sendport
|
||||
final completer = Completer<SendPort>();
|
||||
|
||||
// ignore: cancel_subscriptions
|
||||
final sub = receivePort.listen((message) {
|
||||
// first message from new isolate will be its SendPort
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(message as SendPort);
|
||||
return;
|
||||
}
|
||||
if (message is GameRoomMessage) {
|
||||
switch (message) {
|
||||
case RoomPingMessage():
|
||||
if (message.dest != PingDestination.client) {
|
||||
_logger.warning('Got room ping meant for server');
|
||||
return;
|
||||
}
|
||||
broadcastToRoom(
|
||||
message.roomUuid,
|
||||
message,
|
||||
);
|
||||
|
||||
case PlayerVoteMessage():
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
case PingMessage():
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
}
|
||||
} else {
|
||||
_logger.info('Unknown message: $message');
|
||||
}
|
||||
});
|
||||
_gameRoomSpSubs[roomUuid] = sub;
|
||||
_gameRoomSendPorts[roomUuid] = await completer.future;
|
||||
_logger.info('Spawned new game room $roomUuid, listening to messages from room.');
|
||||
return Status.success;
|
||||
} catch (e) {
|
||||
_logger.severe('Failed to spawn game room isolate', e, StackTrace.current);
|
||||
_gameRoomSendPorts.remove(roomUuid);
|
||||
final sub = _gameRoomSpSubs.remove(roomUuid);
|
||||
await sub?.cancel();
|
||||
}
|
||||
return Status.failure;
|
||||
}
|
||||
}
|
||||
|
||||
enum Status { success, failure }
|
||||
Reference in New Issue
Block a user