Retour au Blog

Concevoir une architecture thread-safe pour l'intégration backend de Godot 4.7 : Résoudre les goulots d'étranglement réseau dans Godot 4.7 RC 3

Publié le 17 juin 2026
Concevoir une architecture thread-safe pour l'intégration backend de Godot 4.7 : Résoudre les goulots d'étranglement réseau dans Godot 4.7 RC 3

En bref

Cet article explique comment concevoir une architecture d'intégration backend thread-safe dans Godot 4.7 RC 3 pour éviter les goulets d'étranglement réseau et les plantages en production. Il propose une implémentation pratique de pool et de file d'attente HTTPRequest en GDScript pour gérer les requêtes de manière asynchrone et non bloquante. Enfin, il aborde la gestion des contraintes spécifiques au web (CORS) et au mobile, tout en présentant les avantages de solutions managées comme horizOn face au développement d'un serveur sur mesure.

Chaque développeur indépendant ayant lancé un jeu multiplayer avec Godot connaît ce moment précis où le client HTTP renvoie un timeout de connexion silencieux ou une exception de thread-safety. Ces problèmes restent souvent invisibles lors des tests en local dans l'éditeur, pour finalement faire crasher le build de production lorsque des centaines de joueurs simultanés sollicitent les endpoints de login. Résoudre ces défaillances nécessite une compréhension approfondie de la couche réseau asynchrone du moteur.

Godot 4.7 RC 3 arrive : Phase finale de stabilisation et corrections des régressions clés

Résolution des régressions critiques dans la Release Candidate

La sortie de Godot 4.7 RC 3 rapproche le moteur d'une version stable très attendue. Actuellement en feature freeze, l'équipe de développement s'est entièrement concentrée sur la résolution des régressions critiques découvertes durant la phase bêta. Pour les développeurs travaillant avec des API externes, ces correctifs garantissent la stabilité du cœur du moteur sous des cycles de vie d'exécution complexes. Plus précisément, la RC 3 corrige un bug de stretch mode sur custom_timeline dans le système d'animation. Elle résout également des bugs de listing d'assets dans l'AssetLib où les licences marquées comme "Other" étaient filtrées de manière incorrecte. Enfin, les développeurs XR apprécieront un correctif pour un crash déclenché par les trackers de marqueurs d'entités spatiales.

Le correctif de mise en file d'attente des événements d'Area dans Jolt Physics

Jolt est devenu le plugin de physique de référence pour Godot 4.x en raison de sa rapidité et de sa stabilité. Cependant, la régression dans les builds bêta forçait la mise en file d'attente des événements d'area lors de la sortie d'un body (body exit), ce qui signifie que chaque fois qu'un rigid body quittait une zone, le moteur effectuait des insertions redondantes dans la file d'attente. Dans un lobby multiplayer au rythme effréné avec 64 joueurs et des dizaines de trigger areas, cette surcharge CPU faisait rapidement chuter le tick rate du serveur de 60Hz à moins de 20Hz. La résolution de cette régression garantit que les vérifications de triggers locaux n'interfèrent pas avec le network thread.

Refonte de l'API REST de l'AssetLib

Un autre point fort de Godot 4.7 est la refonte de l'API de l'Asset Library (AssetLib). La connexion au backend a été portée vers une structure REST modernisée, résolvant les problèmes d'ordre de sortie et les échecs de chargement. Cette mise à niveau sert de modèle sur la façon dont les développeurs de jeux devraient structurer leurs propres intégrations d'API externes. En utilisant des endpoints clairs et des requêtes paginées, vous pouvez optimiser les temps de chargement pour la distribution de contenu. L'utilisation de tableaux JSON avec des structures de schéma unifiées évite les goulots d'étranglement de désérialisation en GDScript.

Pourquoi l'intégration backend dans Godot 4.7 nécessite une attention architecturale particulière

Les limites du nœud HTTPRequest

L'intégration d'un backend avec Godot 4.7 implique la gestion d'opérations asynchrones sans bloquer le main thread du jeu. Godot s'appuie sur des nœuds non bloquants comme HTTPRequest pour gérer les appels, mais ces nœuds présentent une limite majeure : ils ne peuvent pas gérer des requêtes concurrentes. Si vous tentez d'appeler request() sur un nœud qui attend actuellement une réponse, le moteur génère une erreur. Cette erreur "Request already in progress" peut bloquer des fonctions de gameplay vitales si elle n'est pas gérée.

Thread safety et modifications du SceneTree

Pour éviter ces conflits, vous devez concevoir une file d'attente ou un pool robuste qui alloue dynamiquement les requêtes aux nœuds libres. De plus, la thread safety est un défi constant lors du traitement des réponses réseau. Si un network thread en arrière-plan tente de modifier directement le SceneTree, Godot va crasher ou présenter des comportements d'état erratiques. Vous devez toujours différer (defer) les modifications d'UI vers le main thread pour garantir la stabilité.

Surmonter les limitations de SSL/TLS et de CORS

La sécurisation des données des joueurs est essentielle ; comme mentionné dans architecting game backends to survive compromises, la protection des données commence par TLS et une authentification côté serveur robuste. La négociation (handshake) avec votre backend doit se faire via des protocoles sécurisés https:// ou wss://, nécessitant des handshakes de certificats SSL/TLS appropriés. Sur les plateformes mobiles, la moindre erreur de configuration réseau peut entraîner des pertes de connexion silencieuses. De plus, les exports web (HTML5) ajoutent une couche de complexité car les navigateurs imposent des règles strictes de Cross-Origin Resource Sharing (CORS).

Le code : Implémenter un pool de requêtes HTTP thread-safe en GDScript 2.0

L'implémentation du Pool Manager en GDScript

Le singleton GDScript suivant fournit un manager HTTP basé sur un pool thread-safe qui évite les erreurs de requêtes superposées. Il instancie dynamiquement un pool de nœuds HTTPRequest et met en file d'attente les requêtes entrantes lorsque tous les nœuds sont occupés. Il utilise également des pratiques d'analyse (parsing) JSON sécurisées pour éviter les crashs à l'exécution dus à des payloads serveur inattendus.

extends Node
class_name BackendHTTPManager

# Maximum concurrent HTTP requests allowed in the pool
const MAX_CONCURRENT_REQUESTS = 4

# Structure to hold queued request data
class PendingRequest:
	var url: String
	var method: HTTPClient.Method
	var headers: PackedStringArray
	var body: String
	var callback: Callable
	
	func _init(p_url: String, p_method: HTTPClient.Method, p_headers: PackedStringArray, p_body: String, p_callback: Callable):
		self.url = p_url
		self.method = p_method
		self.headers = p_headers
		self.body = p_body
		self.callback = p_callback

# Internal tracking
var _request_pool: Array[HTTPRequest] = []
var _active_requests: Dictionary = {}
var _request_queue: Array[PendingRequest] = []

func _ready() -> void:
	# Initialize the HTTPRequest pool
	for i in range(MAX_CONCURRENT_REQUESTS):
		var http_node = HTTPRequest.new()
		add_child(http_node)
		http_node.request_completed.connect(_on_request_completed.bind(http_node))
		_request_pool.append(http_node)

## Queue an asynchronous HTTP request
func send_request(url: String, method: HTTPClient.Method, headers: PackedStringArray, body: String, callback: Callable) -> void:
	var new_req = PendingRequest.new(url, method, headers, body, callback)
	_request_queue.append(new_req)
	_process_queue()

# Process next items in the queue if a pool node is free
func _process_queue() -> void:
	if _request_queue.is_empty():
		return
		
	# Find an idle HTTPRequest node
	var free_node: HTTPRequest = null
	for node in _request_pool:
		if not _active_requests.has(node):
			free_node = node
			break
			
	if free_node == null:
		# All nodes are busy; request remains in queue
		return
		
	var req = _request_queue.pop_front()
	_active_requests[free_node] = req
	
	var err = free_node.request(req.url, req.headers, req.method, req.body)
	if err != OK:
		# Immediately notify failure if the request failed to initiate
		_active_requests.erase(free_node)
		req.callback.call_deferred(false, -1, {}, "Failed to initiate request")
		_process_queue()

# Callback triggered when a request completes
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, http_node: HTTPRequest) -> void:
	if not _active_requests.has(http_node):
		return
		
	var req = _active_requests[http_node]
	_active_requests.erase(http_node)
	
	var response_string = body.get_string_from_utf8()
	var parsed_data = {}
	var success = (result == HTTPRequest.RESULT_SUCCESS) and (response_code >= 200 and response_code < 300)
	var error_message = ""
	
	if success:
		var json = JSON.new()
		var parse_err = json.parse(response_string)
		if parse_err == OK:
			if typeof(json.data) == TYPE_DICTIONARY:
				parsed_data = json.data
			else:
				success = false
				error_message = "Parsed JSON is not a dictionary"
						else:
				success = false
				error_message = "JSON parse error code: " + str(parse_err)
	else:
		error_message = "HTTP error code: " + str(response_code) + " or result failure: " + str(result)
				# Execute callback on the main thread safely
	req.callback.call_deferred(success, response_code, parsed_data, error_message)
	
	# Process next queued request
	_process_queue()

Analyse détaillée de la logique de pool et de file d'attente

Ce manager initialise un tableau statique de nœuds enfants HTTPRequest lors du callback _ready(). En limitant la taille du pool à une constante définie telle que MAX_CONCURRENT_REQUESTS, vous contrôlez la congestion réseau côté client. Chaque nœud est lié à un gestionnaire de réponses central, transmettant sa propre référence pour suivre quelle requête s'est terminée.

La fonction send_request() ajoute une classe wrapper contenant l'URL, le payload et un callback appelable (callable) à une file d'attente FIFO. Lorsqu'une requête se termine, le nœud du pool est marqué comme inactif (idle), et le manager traite immédiatement l'élément suivant dans la file d'attente. Cela élimine complètement l'erreur de requête superposée.

Parser des payloads JSON en toute sécurité dans Godot 4.7

Le parsing de données dans Godot 4.7 a changé par rapport aux versions majeures précédentes. Nous instancions un nouvel objet JSON et appelons parse() plutôt que d'utiliser des fonctions globales obsolètes. En vérifiant que json.data est de type TYPE_DICTIONARY, nous évitons que le client ne crashe si le backend renvoie une réponse malformée.

Enfin, nous utilisons call_deferred pour exécuter le callback sur le main thread. Cela garantit que toutes les mises à jour de l'UI ou les modifications du SceneTree déclenchées par la réponse réseau se produisent en toute sécurité. Exécuter ces callbacks de manière asynchrone évite les conflits de threading et préserve la fluidité du framerate.

Résoudre les obstacles d'intégration spécifiques aux plateformes : Web et Mobile

Exports Web et contraintes WASM single-thread

Les cibles WebAssembly ne se comportent pas comme les builds de bureau. Dans les environnements de navigation, Godot s'exécute dans une boucle single-thread à moins que des en-têtes SharedArrayBuffer spécifiques ne soient configurés. Si vous utilisez des opérations HTTP bloquantes et synchrones, toute la fenêtre du navigateur se figera, ce qui entraînera une expérience utilisateur désastreuse.

Pour éviter cela, utilisez toujours des requêtes non bloquantes et basées sur des signaux pour vos joueurs web. Vous devez également vous assurer que vos endpoints backend sont configurés pour envoyer les en-têtes CORS appropriés. Spécifiquement, les en-têtes Access-Control-Allow-Origin et Access-Control-Allow-Headers doivent explicitement autoriser le domaine qui héberge votre jeu web.

Exports Android et permissions réseau

Les cibles mobiles présentent leurs propres défis. Pour les exports Android, oublier de cocher la permission INTERNET dans le preset d'export est un oubli courant qui désactive tous les appels réseau. De plus, Godot 4.7 introduit une personnalisation affinée pour les splash screens Android et le redimensionnement de fenêtre, ce qui permet d'éviter les bugs d'affichage lors des premières vérifications réseau au démarrage.

Si vous exportez vers des plateformes mobiles, il est essentiel de comprendre le paysage réglementaire. Gérer la facturation tierce (third-party billing) nécessite des handshakes d'API sécurisés, comme décrit dans notre guide sur architecting third party mobile billing. Garantir la sécurité de vos callbacks de facturation et des handshakes clients empêche les joueurs de contourner les microtransactions.

Bonnes pratiques pour mettre à l'échelle l'intégration backend de Godot 4.7

1. Implémenter un Exponential Backoff avec Jitter

Évitez de surcharger votre serveur en cas de tempête de reconnexions (reconnection storms). Si un joueur perd la connexion, ne réessayez pas immédiatement à intervalles fixes. Au lieu de cela, multipliez le délai de tentative par 1,5 ou 2,0 à chaque fois, et ajoutez un léger décalage aléatoire (jitter) pour éviter que tous les clients ne réessayent en même temps.

Par exemple, si la première tentative est à 1,0 seconde, les suivantes doivent avoir lieu à 2,0, 4,0 et 8,0 secondes. Pour éviter que tous les clients déconnectés ne se reconnectent à la milliseconde près, ajoutez un float aléatoire entre 0,1 et 0,5 seconde à chaque délai. Cela répartit la charge sur vos serveurs backend et évite les pannes d'API en cascade lors des interruptions.

2. Valider les payloads des deux côtés

Ne faites jamais confiance aux données client, et ne supposez jamais que les données du serveur sont parfaitement formées. Validez tous les dictionnaires et toutes les clés avant de les lire en GDScript. De même, assurez-vous que votre backend valide les corps des requêtes entrantes pour éviter les vulnérabilités par injection SQL ou exécution de code à distance.

Une vulnérabilité courante dans les jeux multiplayer est la confiance accordée au client. Si votre script client reçoit un dictionnaire du backend, utilisez Dictionary.has() pour vérifier chaque clé requise avant d'y accéder. Accéder à une clé manquante en GDScript lève une erreur à l'exécution, ce qui interrompt l'exécution du script. Cette validation empêche votre UI de planter lorsque les endpoints du serveur sont mis à jour.

3. Découpler les services réseau de l'UI

Évitez d'écrire du code réseau au sein de vos scripts d'UI. Créez un singleton d'autoload dédié pour gérer tout le trafic HTTP et la gestion d'état. Votre UI doit uniquement se connecter aux signaux émis par ce manager, ce qui maintient votre code frontend modulaire et testable.

Par exemple, lorsqu'un joueur ouvre un écran d'inventaire, l'UI doit émettre un signal personnalisé pour demander les données. L'autoload réseau capture ce signal, effectue l'appel HTTP et émet un signal de succès une fois le dictionnaire renseigné. L'UI écoute ce signal de succès pour remplir ses grilles d'inventaire. Ce découplage maintient votre UI réactive et simplifie le débogage de la logique réseau.

4. Effectuer des tests CORS Pre-Flight au plus tôt

Testez toujours votre jeu sur un serveur web local avec CORS activé avant de le publier sur des plateformes comme itch.io. De nombreux développeurs compilent leur jeu pour s'apercevoir ensuite que les appels HTTP échouent sur le web en raison des politiques d'origine. Un test précoce évite les urgences de configuration le jour du lancement.

Les navigateurs envoient une requête HTTP OPTIONS en tant que vérification de pre-flight avant d'exécuter des requêtes POST. Si votre backend n'est pas configuré pour répondre aux requêtes OPTIONS avec un statut 200 OK, le navigateur bloque la requête suivante. Vous devriez vérifier les logs de la console du navigateur (F12) pour diagnostiquer ces problèmes d'origine. Détecter ces erreurs en phase de staging prévient les problèmes de login multiplayer lors du lancement.

Standardiser l'infrastructure backend : Développement sur mesure vs Solutions managées

Le coût réel de la création de serveurs de jeu à partir de zéro

Créer un backend personnalisé pour votre jeu Godot exige un effort considérable. Vous devez écrire du code serveur, implémenter l'authentification par JWT Token, configurer l'indexation de base de données et gérer des load balancers. Mettre en place cette infrastructure manuellement peut prendre de 4 à 6 semaines de développement dédié, ce qui vous détourne du gameplay. Un backend de production sécurisé nécessite du clustering de base de données, l'expiration des tokens d'authentification et des migrations de schémas de base de données. Vous devez également écrire des scripts personnalisés pour surveiller la santé des serveurs et consigner les anomalies réseau.

Accélérer le développement avec horizOn

L'utilisation du SDK horizOn élimine ces frictions administratives. La plateforme gère la persistance des utilisateurs, la télémétrie et les leaderboards en arrière-plan, vous permettant de vous concentrer sur le jeu. Au lieu de déboguer des pools de connexion HTTP, vous effectuez de simples appels d'API qui s'exécutent sur une infrastructure optimisée. Cela vous permet de déployer des fonctionnalités auprès de vos joueurs en quelques minutes plutôt qu'en plusieurs semaines. Mettre en place cette infrastructure manuellement peut prendre de 4 à 6 semaines de développement dédié. Avec horizOn, ces services backend sont préconfigurés, vous laissant publier votre jeu plutôt que votre infrastructure.

Prêt à mettre à l'échelle votre backend multiplayer ? Essayez horizOn gratuitement ou consultez la documentation de l'API pour démarrer votre prochain projet.


Source : Release candidate: Godot 4.7 RC 3