@@ -577,19 +577,33 @@ defmodule Codebattle.Tournament.Base do
577577 end
578578
579579 defp start_round ( tournament , round_params \\ % { } ) do
580+ # Perform initial updates in a single operation
581+ tournament =
582+ update_struct ( tournament , % {
583+ break_state: "off" ,
584+ last_round_started_at: NaiveDateTime . utc_now ( :second ) ,
585+ match_timeout_seconds: Map . get ( round_params , :timeout_seconds , tournament . match_timeout_seconds )
586+ } )
587+
588+ # Build and save round first - this is a critical operation
589+ tournament = build_and_save_round! ( tournament )
590+
591+ # Perform these operations in sequence as they depend on each other
592+ tournament =
593+ tournament
594+ |> maybe_preload_tasks ( )
595+ |> maybe_set_round_task_ids ( )
596+ |> maybe_start_round_timer ( )
597+ |> maybe_activate_players ( )
598+
599+ # Build matches - this is the most time-consuming part
600+ tournament = build_round_matches ( tournament , round_params )
601+
602+ # Save to database
603+ tournament = db_save! ( tournament )
604+
605+ # These operations can be done after the critical path
580606 tournament
581- |> update_struct ( % {
582- break_state: "off" ,
583- last_round_started_at: NaiveDateTime . utc_now ( :second ) ,
584- match_timeout_seconds: Map . get ( round_params , :timeout_seconds , tournament . match_timeout_seconds )
585- } )
586- |> build_and_save_round! ( )
587- |> maybe_preload_tasks ( )
588- |> maybe_set_round_task_ids ( )
589- |> maybe_start_round_timer ( )
590- |> maybe_activate_players ( )
591- |> build_round_matches ( round_params )
592- |> db_save! ( )
593607 |> maybe_start_waiting_room ( )
594608 |> broadcast_round_created ( )
595609 end
@@ -648,40 +662,51 @@ defmodule Codebattle.Tournament.Base do
648662 defp bulk_create_round_games_and_matches ( batch , tournament , task , timeout_seconds ) do
649663 reset_task_ids = tournament . task_provider == "task_pack_per_round"
650664
651- batch
652- |> Enum . map ( fn
653- # TODO: skip bots game
654- # {[p1 = %{is_bot: true}, p2 = %{is_bot: true}], match_id} ->
655- # Tournament.Matches.put_match(tournament, %Tournament.Match{
656- # id: match_id,
657- # state: "canceled",
658- # round_id: tournament.current_round_id,
659- # round_position: tournament.current_round_position,
660- # player_ids: Enum.sort([p1.id, p2.id])
661- # })
662-
663- { [ p1 , p2 ] = players , match_id } ->
664- % {
665- players: players ,
666- ref: match_id ,
667- round_id: tournament . current_round_id ,
668- state: "playing" ,
669- task: task ,
670- waiting_room_name: tournament . waiting_room_name ,
671- timeout_seconds: timeout_seconds ,
672- tournament_id: tournament . id ,
673- type: game_type ( ) ,
674- use_chat: tournament . use_chat ,
675- use_timer: tournament . use_timer
676- }
677- |> maybe_set_free_task ( tournament , p1 )
678- |> maybe_add_award ( tournament )
679- end )
680- |> Game.Context . bulk_create_games ( )
681- |> Enum . zip ( batch )
682- |> Enum . each ( fn { game , { players , _match_id } } ->
683- build_and_run_match ( tournament , players , game , reset_task_ids )
684- end )
665+ # Prepare game creation parameters in a single pass
666+ game_params =
667+ Enum . map ( batch , fn
668+ { [ p1 , p2 ] = players , match_id } ->
669+ base_params = % {
670+ players: players ,
671+ ref: match_id ,
672+ round_id: tournament . current_round_id ,
673+ state: "playing" ,
674+ task: task ,
675+ waiting_room_name: tournament . waiting_room_name ,
676+ timeout_seconds: timeout_seconds ,
677+ tournament_id: tournament . id ,
678+ type: game_type ( ) ,
679+ use_chat: tournament . use_chat ,
680+ use_timer: tournament . use_timer
681+ }
682+
683+ # Apply transformations
684+ params =
685+ base_params
686+ |> maybe_set_free_task ( tournament , p1 )
687+ |> maybe_add_award ( tournament )
688+
689+ { params , players , match_id }
690+ end )
691+
692+ # Extract just the game parameters for bulk creation
693+ game_creation_params = Enum . map ( game_params , fn { params , _players , _match_id } -> params end )
694+
695+ # Create games in bulk
696+ created_games = Game.Context . bulk_create_games ( game_creation_params )
697+
698+ # Process matches in parallel using Task.async_stream with controlled concurrency
699+ created_games
700+ |> Enum . zip ( game_params )
701+ |> Task . async_stream (
702+ fn { game , { _params , players , _match_id } } ->
703+ build_and_run_match ( tournament , players , game , reset_task_ids )
704+ end ,
705+ max_concurrency: System . schedulers_online ( ) ,
706+ ordered: false ,
707+ timeout: 30_000
708+ )
709+ |> Stream . run ( )
685710 end
686711
687712 defp create_rematch_game ( tournament , players , ref ) do
@@ -1026,43 +1051,77 @@ defmodule Codebattle.Tournament.Base do
10261051 matches_to_finish = get_matches ( tournament , "playing" )
10271052 finished_at = DateTime . utc_now ( :second )
10281053
1029- Enum . each (
1030- matches_to_finish ,
1031- fn match ->
1032- duration_sec = NaiveDateTime . diff ( finished_at , match . started_at )
1033-
1034- player_results = improve_player_results ( tournament , match , duration_sec )
1035- Game.Context . trigger_timeout ( match . game_id )
1054+ # Early return if no matches to finish
1055+ if matches_to_finish == [ ] do
1056+ tournament
1057+ else
1058+ # Process matches in parallel with Task.async_stream
1059+ match_results =
1060+ matches_to_finish
1061+ |> Task . async_stream (
1062+ fn match ->
1063+ duration_sec = NaiveDateTime . diff ( finished_at , match . started_at )
1064+
1065+ # Get player results and trigger timeout
1066+ player_results = improve_player_results ( tournament , match , duration_sec )
1067+ Game.Context . trigger_timeout ( match . game_id )
1068+
1069+ # Create new match with timeout state
1070+ new_match = % {
1071+ match
1072+ | state: "timeout" ,
1073+ player_results: player_results ,
1074+ duration_sec: duration_sec ,
1075+ finished_at: finished_at
1076+ }
1077+
1078+ # Return match and player data for batch processing
1079+ { new_match , player_results }
1080+ end ,
1081+ max_concurrency: System . schedulers_online ( ) * 2 ,
1082+ timeout: 10_000
1083+ )
1084+ |> Enum . to_list ( )
10361085
1037- new_match = % {
1038- match
1039- | state: "timeout" ,
1040- player_results: player_results ,
1041- duration_sec: duration_sec ,
1042- finished_at: finished_at
1043- }
1086+ # Batch update matches and collect player updates
1087+ player_updates =
1088+ Enum . reduce ( match_results , % { } , fn { :ok , { new_match , player_results } } , acc ->
1089+ # Update match in tournament
1090+ Tournament.Matches . put_match ( tournament , new_match )
10441091
1045- Tournament.Matches . put_match ( tournament , new_match )
1092+ # Broadcast match update
1093+ Codebattle.PubSub . broadcast ( "tournament:match:upserted" , % {
1094+ tournament: tournament ,
1095+ match: new_match
1096+ } )
10461097
1047- Codebattle.PubSub . broadcast ( "tournament:match:upserted" , % {
1048- tournament: tournament ,
1049- match: new_match
1050- } )
1098+ # Collect player updates
1099+ Enum . reduce ( player_results , acc , fn { player_id , result } , player_acc ->
1100+ player_score = result . score
1101+ player_lang = result . lang
10511102
1052- player_results
1053- |> Map . keys ( )
1054- |> Enum . each ( fn player_id ->
1055- player = Tournament.Players . get_player ( tournament , player_id )
1056-
1057- player &&
1058- Tournament.Players . put_player ( tournament , % {
1059- player
1060- | score: player . score + player_results [ player_id ] . score ,
1061- lang: player_results [ player_id ] . lang
1062- } )
1103+ # credo:disable-for-next-line Credo.Check.Refactor.Nesting
1104+ Map . update ( player_acc , player_id , % { score: player_score , lang: player_lang } , fn existing ->
1105+ % { score: existing . score + player_score , lang: player_lang }
1106+ end )
1107+ end )
10631108 end )
1064- end
1065- )
1109+
1110+ # Batch update player scores
1111+ Enum . each ( player_updates , fn { player_id , updates } ->
1112+ player = Tournament.Players . get_player ( tournament , player_id )
1113+
1114+ if player do
1115+ Tournament.Players . put_player ( tournament , % {
1116+ player
1117+ | score: player . score + updates . score ,
1118+ lang: updates . lang
1119+ } )
1120+ end
1121+ end )
1122+
1123+ tournament
1124+ end
10661125 end
10671126
10681127 defp improve_player_results ( tournament , match , duration_sec ) do
0 commit comments