Files
ruby-sea-battle/main-server/app/services/matchmaking_service.rb
T
2026-09-22 09:07:13 +03:00

123 lines
3.3 KiB
Ruby

class MatchmakingService
QUEUE_KEY = "matchmaking:queue"
LOCK_KEY = "matchmaking:lock"
MATCH_PREFIX = "matchmaking:match:"
MATCH_TTL = 60 # seconds
POLL_INTERVAL = 0.5 # seconds
MAX_WAIT = 30 # seconds
# Game server to redirect players to (hardcoded for now)
GAME_SERVER = ENV.fetch("GAME_SERVER_URL", "ws://game-server.example.com:8080")
class << self
# Main entry point: waits for an opponent and returns match data
def find_match(user_id)
user_id = user_id.to_i
# Try to pair with an existing waiting player
opponent_id = pop_opponent(user_id)
if opponent_id
create_match(user_id, opponent_id)
else
# Add self to queue and wait
add_to_queue(user_id)
wait_for_match(user_id)
end
end
# Cancel matchmaking (remove from queue)
def cancel(user_id)
RedisConnection.with do |redis|
redis.lrem(QUEUE_KEY, 0, user_id)
end
end
private
# Atomically pop an opponent from the queue (excluding self)
def pop_opponent(user_id)
RedisConnection.with do |redis|
redis.watch(QUEUE_KEY) do
queue = redis.lrange(QUEUE_KEY, 0, -1)
opponent = queue.find { |id| id.to_i != user_id }
if opponent
result = redis.multi do |tx|
tx.lrem(QUEUE_KEY, 0, opponent)
end
return opponent.to_i if result&.first&.positive?
else
redis.unwatch
end
end
end
nil
end
def add_to_queue(user_id)
RedisConnection.with do |redis|
# Remove any stale entries first
redis.lrem(QUEUE_KEY, 0, user_id)
redis.rpush(QUEUE_KEY, user_id)
end
end
def wait_for_match(user_id)
start_time = Time.current
match_id = nil
while Time.current - start_time < MAX_WAIT
RedisConnection.with do |redis|
match_id = redis.get("#{MATCH_PREFIX}#{user_id}")
end
break if match_id
sleep POLL_INTERVAL
end
if match_id
# Retrieve and delete the match data
data = nil
RedisConnection.with do |redis|
data = redis.get("#{MATCH_PREFIX}match:#{match_id}")
redis.del("#{MATCH_PREFIX}#{user_id}")
redis.del("#{MATCH_PREFIX}match:#{match_id}")
end
JSON.parse(data, symbolize_names: true) if data
else
# Timeout: remove from queue
cancel(user_id)
nil
end
end
def create_match(player1_id, player2_id)
match_id = SecureRandom.uuid
game_token = JwtService.encode(
match_id: match_id,
player1_id: player1_id,
player2_id: player2_id,
game_server: GAME_SERVER,
exp: 2.minute.from_now.to_i
)
match_data = {
match_id: match_id,
game_token: game_token,
game_server: GAME_SERVER,
players: [ player1_id, player2_id ]
}
# Store match data for both players
RedisConnection.with do |redis|
redis.set("#{MATCH_PREFIX}match:#{match_id}", match_data.to_json, ex: MATCH_TTL)
redis.set("#{MATCH_PREFIX}#{player1_id}", match_id, ex: MATCH_TTL)
redis.set("#{MATCH_PREFIX}#{player2_id}", match_id, ex: MATCH_TTL)
end
match_data
end
end
end