Aujourd'hui, lorsque Node.js bat son plein, nous pouvons déjà l'utiliser pour faire toutes sortes de choses. Il y a quelque temps, Up Host a participé à l'événement Geek Song. Au cours de cet événement, nous avons l'intention de créer un jeu qui permet aux «personnes tête en bas» de communiquer davantage. La fonction principale est l'interaction multi-personnes en temps réel du concept LAN Party. La course des geeks n'a été que 36 heures pitoyablement courtes, ce qui nécessite que tout soit rapide et rapide. Dans une telle prémisse, la préparation initiale semble un peu "naturelle". Solution d'application multiplateforme Nous avons choisi Node-Webkit, ce qui est assez simple et répond à nos exigences.
Selon les exigences, notre développement peut être effectué séparément en fonction des modules. Cet article décrit spécifiquement le processus de développement de l'espace-toirome (notre cadre de jeu multijoueur en temps réel), y compris une série d'explorations et de tentatives, ainsi que des solutions à certaines restrictions sur la plate-forme Node.js et WebKit elle-même, et la proposition de solutions.
Commencer
Spacerom regard
Au début, la conception de Spaceroom était définitivement axée sur la demande. Nous espérons que ce cadre pourra fournir les fonctions de base suivantes:
Peut distinguer un groupe d'utilisateurs en fonction des pièces (ou des canaux)
Capable de recevoir des instructions des utilisateurs du groupe de collecte
Lors de la correspondance entre chaque client, les données du jeu peuvent être diffusées avec précision en fonction de l'intervalle spécifié
Peut éliminer autant que possible l'impact de la latence du réseau
Bien sûr, dans les étapes ultérieures du codage, nous avons fourni à l'espaceroom avec plus de fonctions, notamment en pause le jeu, générant des nombres aléatoires cohérents entre chaque client, etc. (bien sûr, ceux-ci peuvent être mis en œuvre par eux-mêmes en fonction des exigences, et il n'est pas nécessaire d'utiliser l'espaceroom, un cadre qui fonctionne au niveau de communication, ce qui est plus du niveau de communication).
Apis
L'espacement est divisé en deux parties: avant et arrière. Le travail requis par le serveur comprend le maintien d'une liste de pièces et la fourniture des fonctions de création et de rejoindre une pièce. Nos API client ressemblent à ceci:
Spaceroom.Connect (adresse, rappel) Connectez-vous au serveur
Spaceroom.Createroom (rappel) Créez une pièce
Spaceroom.
spaceroom.on (événement, rappel) écoute les événements
...
Une fois que le client s'est connecté au serveur, il reçoit divers événements. Par exemple, un utilisateur dans une pièce peut recevoir un événement où un nouveau joueur se joint, ou un événement où le jeu commence. Nous donnons au client un «cycle de vie», qui sera dans l'un des états suivants à tout moment:
Vous pouvez obtenir l'état actuel du client via Spaceeroom.State.
L'utilisation d'un framework côté serveur est relativement simple. Si vous utilisez le fichier de configuration par défaut, vous pouvez exécuter directement le framework côté serveur. Nous avons une exigence de base: le code du serveur peut s'exécuter directement dans le client sans avoir besoin d'un serveur séparé. Les joueurs qui ont joué au PS ou PSP devraient savoir de quoi je parle. Bien sûr, il est également excellent d'exécuter sur un serveur spécial.
L'implémentation du code logique est brève ici. La première génération d'espaceur a complété la fonction d'un serveur de socket, qui maintient une liste de pièces, y compris l'état de la salle, et les communications de temps de jeu correspondant à chaque pièce (collection d'instructions, diffusion de seau, etc.). Pour une implémentation spécifique, veuillez vous référer au code source.
Algorithme synchrone
Alors, comment pouvons-nous rendre les choses affichées entre chaque client cohérent en temps réel?
Cette chose semble intéressante. Pensez-y attentivement, que avons-nous besoin du serveur pour nous aider à livrer? Naturellement, vous réfléchissez à ce qui peut provoquer des incohérences logiques entre les différents clients: les instructions des utilisateurs. Étant donné que les codes qui traitent de la logique du jeu sont les mêmes, compte tenu des mêmes conditions, le code exécute le même résultat. La seule différence est les différentes commandes de joueurs reçus pendant le jeu. D'accord, nous avons besoin d'un moyen de synchroniser ces instructions. Si tous les clients peuvent obtenir la même instruction, tous les clients peuvent théoriquement avoir le même résultat d'opération.
Les algorithmes de synchronisation des jeux en ligne sont toutes sortes de scénarios étranges et applicables. Spaceroom utilise des algorithmes de synchronisation similaires au concept de verrouillage de trame. Nous divisons la chronologie en intervalles un par un, et chaque intervalle est appelé seau. Le seau est utilisé pour charger des instructions et est maintenu par le côté serveur. À la fin de chaque période de temps, le serveur diffuse le seau à tous les clients. Une fois que le client a obtenu le seau, les instructions sont récupérées, puis exécutées après vérification.
Afin de réduire l'impact du retard du réseau, chaque instruction reçue par le serveur du client sera livrée au seau correspondant en fonction d'un certain algorithme, et les étapes suivantes sont spécifiquement suivies:
Soit Order_Start le moment de la commande transportée par la commande, et t est l'heure de début du seau où se trouve l'Ordre_start.
Si t + delay_time <= l'heure de début du seau qui collecte actuellement l'instruction, livrez la commande au seau qui collecte actuellement l'instruction, sinon continuer l'étape 3
Livrer l'instruction au seau correspondant de t + delay_time
où Delay_time est le temps de retard du serveur convenu, qui peut être considéré comme le retard moyen entre les clients. La valeur par défaut dans l'espaceroom est de 80, et la valeur par défaut de la longueur du seau est de 48. À la fin de chaque période de temps, le serveur diffuse ce seau à tous les clients et commence à recevoir des instructions pour le seau suivant. Le client contrôle l'erreur de temps dans une plage acceptable lors de l'exécution automatique de la correspondance dans la logique en fonction de l'intervalle de seau reçu.
Cela signifie que dans des circonstances normales, le client recevra un seau envoyé du serveur toutes les 48 ms. Lorsque le moment où le seau doit être traité, le client le gérera en conséquence. En supposant que le client FPS = 60, il recevra un seau toutes les 3 images environ, et la logique sera mise à jour en fonction de ce seau. Si le seau n'a pas été reçu après l'expiration du temps en raison des fluctuations du réseau, le client fait une pause sur la logique du jeu et les attentes. À la fois dans un seau, la logique peut être mise à jour à l'aide de la méthode LERP.
Dans le cas de Delay_time = 80, Bucket_Size = 48, l'une ou l'autre instruction sera retardée d'au moins 96 ms d'exécution. Modifiez ces deux paramètres, par exemple, dans le cas de Delay_time = 60, Bucket_Size = 32, l'une ou l'autre instruction sera retardée d'au moins 64 ms.
Un incident sanglant causé par une minuterie
En regardant le tout, notre cadre doit avoir une minuterie précise lorsqu'elle s'exécute. Exécutez la diffusion du seau dans un intervalle fixe. Bien sûr, nous avons d'abord pensé à utiliser SetInterval (), mais la seconde suivante, nous avons réalisé à quel point cette idée est peu fiable: le Naughty SetInterval () semble avoir des erreurs très graves. Et ce qui est si mauvais, c'est que chaque erreur s'accumulera, provoquant des conséquences de plus en plus graves.
Nous avons donc immédiatement pensé à utiliser setTimeout () pour rendre notre logique à peu près stable autour de l'intervalle spécifié en corrigeant dynamiquement l'heure de l'arrivée suivante. Par exemple, cette fois que SetTimeout () est de 5 ms de moins que prévu, nous allons donc le laisser 5 ms à l'avance la prochaine fois. Cependant, les résultats des tests ne sont pas satisfaisants, et ce n'est pas assez élégant, peu importe comment vous le regardez.
Nous devons donc changer notre réflexion. Est-il possible de faire expirer SetTimeout () le plus rapidement possible, puis nous vérifions si l'heure actuelle a atteint l'heure cible. Par exemple, dans notre boucle, en utilisant Settimeout (rappel, 1) pour continuer à vérifier l'heure, ce qui semble être une bonne idée.
Minuterie décevante
Nous avons immédiatement écrit un morceau de code pour tester nos idées, et les résultats ont été décevants. Dans la dernière version stable Node.js actuelle (V0.10.32) et la plate-forme Windows, exécutez ce morceau de code:
La copie de code est la suivante:
var sum = 0, count = 0;
fonction test () {
var maintenant = date.Now ();
setTimeout (function () {
var diffe = date.now () - maintenant;
sum + = diff;
Count ++;
test();
});
}
test();
Après une période de temps, entrez la somme / compter dans la console et vous pouvez voir un résultat, similaire à:
La copie de code est la suivante:
> Sum / Count
15.624555160142348
Quoi?! Je veux un intervalle de 1 ms, mais vous me dites que l'intervalle moyen réel est de 15,625 ms! Cette image est tout simplement trop belle. Nous avons fait le même test sur Mac et le résultat était de 1,4 ms. Alors nous étions confus: qu'est-ce que c'est? Si j'étais un fan d'Apple, j'aurais pu conclure que Windows était trop poubelle et abandonné sur Windows. Heureusement, j'étais un ingénieur frontal rigoureux, alors j'ai commencé à continuer de penser à ce numéro.
Attendez, pourquoi ce numéro est-il si familier? Ce nombre sera-t-il trop similaire à l'intervalle de minuterie maximum sous Windows? J'ai immédiatement téléchargé une horloge pour tester, et après avoir exécuté la console, j'ai obtenu les résultats suivants:
La copie de code est la suivante:
Intervalle de minuterie maximum: 15,625 ms
Intervalle de minuterie minimum: 0,500 ms
Intervalle de minuterie actuel: 1,001 ms
Effectivement! En regardant le manuel Node.js, nous pouvons voir une description de Settimeout comme ceci:
Le retard réel dépend de facteurs externes tels que la granularité du temporisateur du système d'exploitation et la charge du système.
Cependant, les résultats des tests montrent que ce retard réel est l'intervalle de minuterie maximum (notez que l'intervalle de temporisation actuel du système n'est que de 1,001 ms), ce qui est inacceptable dans tous les cas. La forte curiosité nous pousse à parcourir le code source de Node.js pour avoir un aperçu de la vérité.
Bug dans node.js
Je crois que la plupart d'entre vous et moi avons une certaine compréhension du mécanisme de boucle uniforme de Node.js. En examinant le code source de la mise en œuvre du temporisateur, nous pouvons comprendre à peu près le principe de mise en œuvre de la minuterie. Commençons par la boucle principale de la boucle d'événements:
La copie de code est la suivante:
while (r! = 0 && LOOP-> stop_flag == 0) {
/ * Mettre à jour le temps mondial * /
uv_update_time (boucle);
/ * Vérifiez si la minuterie expire et exécutez le rappel de minuterie correspondant * /
uv_process_timers (boucle);
/ * Appelez les rappels inactifs si rien à faire. * /
if (LOOP-> en attente_reqs_tail == null &&
LOOP-> endgame_handles == null) {
/ * Empêcher la boucle d'événement de quitter * /
uv_idle_invoke (boucle);
}
uv_process_reqs (boucle);
uv_process_endgames (Loop);
uv_prepare_invoke (boucle);
/ * Collectez les événements IO * /
(* Poll) (Loop, Loop-> idle_handles == null &&
LOOP-> en attente_reqs_tail == null &&
LOOP-> endgame_handles == null &&
! Loop-> stop_flag &&
(Loop-> active_handles> 0 ||
! ngx_queue_empty (& loop-> active_reqs)) &&
! (mode & uv_run_nowait));
/ * setImMediate () etc * /
uv_check_invoke (boucle);
r = uv__loop_alive (boucle);
if (mode & (uv_run_once | uv_run_nowait)))
casser;
}
Le code source de la fonction uv_update_time est le suivant: (https://github.com/joyent/libuv/blob/v0.10/src/win/timer.c))
La copie de code est la suivante:
void uv_update_time (UV_LOOP_T * LOOP) {
/ * Obtenez l'heure du système actuel * /
DWORD TickS = getTickCount ();
/ * L'hypothèse est faite que Large_Integer.quadPart a le même type * /
/ * LOOP-> Temps, ce qui se trouve être. Existe-t-il un moyen d'affirmer cela? * /
Large_Integer * time = (Large_Integer *) & Loop-> time;
/ * Si la minuterie est enveloppée, ajoutez 1 à son DWORD de haut niveau. * /
/ * uv_poll doit s'assurer que la minuterie ne peut jamais déborder plus que * /
/ * une fois entre deux appels uv_update_time ultérieurs. * /
if (ticks <time-> lowpart) {
Time-> HighPart + = 1;
}
Time-> LowPart = ticks;
}
L'implémentation interne de cette fonction utilise la fonction getTickCount () de Windows pour définir l'heure actuelle. Autrement dit, après avoir appelé la fonction Settimeout, après une série de difficultés, le minuteur interne-> dû sera défini sur le temps de boucle actuel + délai d'expiration. Dans la boucle de l'événement, mettez d'abord à mettre à jour l'heure de la boucle actuelle via UV_UPDate_time, puis vérifiez si le temporisateur expire dans UV_PROCESS_TIMES. Si c'est le cas, entrez dans le monde de JavaScript. Après avoir lu l'intégralité de l'article, la boucle d'événement est à peu près comme ce processus:
Mettre à jour le temps mondial
Vérifiez la minuterie. Si la minuterie expire, exécutez le rappel.
Vérifiez la file d'attente REQ et exécutez la demande d'attente
Entrez la fonction de scrutin pour collecter des événements IO. Si un événement IO arrive, ajoutez la fonction de traitement correspondante à la file d'attente REQS pour exécution dans la boucle d'événement suivante. À l'intérieur de la fonction de scrutin, une méthode système est appelée pour collecter des événements IO. Cette méthode entraînera un blocage du processus jusqu'à l'arrivée d'un événement IO ou que l'heure du délai d'exécution soit atteinte. Lorsque cette méthode est appelée, le délai d'expiration est défini au moment où le temporisateur le plus récent expire. Cela signifie que les événements IO sont collectés par blocage et le temps de blocage maximal est le dernier moment de la prochaine minuterie.
Code source de l'une des fonctions de sondage sous Windows:
La copie de code est la suivante:
Loop statique void uv_poll (uv_loop_t *, int block) {
DWORD octets, délai d'expiration;
Clé ulong_ptr;
Chevauché * chevauché;
uv_req_t * req;
if (block) {
/ * Retirer le temps d'expiration du minuteur le plus récent * /
timeout = uv_get_poll_timeout (Loop);
} autre {
timeout = 0;
}
GetQueuedCompletionStatus (LOOP-> IOCP,
& octets,
&clé,
& se chevauchant,
/ * Au maximum bloquant jusqu'à l'expiration du minuteur suivant * /
temps mort);
if (chevauché) {
/ * Le paquet a été désactivé * /
req = uv_overlapt_to_req (chevauché);
/ * Insérer des événements IO dans la file d'attente * /
uv_insert_pening_req (Loop, req);
} else if (getlasterror ()! = wait_timeout) {
/ * Erreur grave * /
uv_fatal_error (getLasterRor (), "getQueuedCompletionStatus");
}
}
En suivant les étapes ci-dessus, en supposant que nous définissons un temporisateur de temps mort = 1 ms, la fonction de scrutin bloquera au plus 1 ms, puis nous récupérons après la récupération (s'il n'y a pas d'événements IO pendant la période). Lorsque vous continuez à entrer la boucle de boucle d'événements, UV_UPDate_time mettra à jour l'heure, puis UV_PROCESS_TIMERS constatera que notre minuterie expire et exécutera le rappel. L'analyse préliminaire est donc que soit le UV_UPDATE_THILE a un problème (l'heure actuelle n'est pas mise à jour correctement), ou la fonction de scrutin attend 1 ms puis récupère. Il y a un problème avec cette attente de 1 ms.
En regardant MSDN, nous avons découvert de manière étonnamment une description de la fonction GetTickCount:
La résolution de la fonction GetTickCount est limitée à la résolution du temporisateur du système, qui se situe généralement dans la fourchette de 10 millions de secondes à 16 millions de secondes.
La précision de GettickCount est si rude! Supposons que la fonction de scrutin bloque correctement l'heure de 1 ms, mais la prochaine fois que UV_UPDate_time est exécutée, le temps de boucle actuel n'est pas correctement mis à jour! Donc, notre minuterie n'a pas été jugée expirée, alors Poll a attendu un autre 1 ms et est entré dans la boucle de l'événement suivante. Jusqu'à ce que GetTickCount soit finalement mis à jour correctement (le soi-disant 15,625 ms est mis à jour une fois), l'heure actuelle de la boucle est mise à jour et notre minuterie est jugée expirée dans UV_Process_timers.
Demandez de l'aide à Webkit
Ce code source de Node.js est très impuissant: il a utilisé une fonction temporelle avec une faible précision et n'a rien fait. Mais nous avons immédiatement pensé que depuis que nous utilisons Node-Webkit, en plus de Settimeout de Node.js, nous avons également Settimeout de Chromium. Écrivez un code de test et utilisez notre navigateur ou notre nœud-webkit pour exécuter: http://marks.lrednight.com/test.html#1 (# suivi du nombre indique que l'intervalle à mesurer). Le résultat est le suivant:
Selon les spécifications HTML5, le résultat théorique devrait être que les 5 premiers résultats sont de 1 ms et les résultats suivants sont 4 ms. Les résultats affichés dans le cas de test commencent à partir de la troisième fois, ce qui signifie que les données sur le tableau doivent théoriquement être 1 ms pendant les trois premières fois, et les résultats par la suite sont de 4 ms. Le résultat a certaines erreurs, et selon les réglementations, le plus petit résultat théorique que nous pouvons obtenir est de 4 ms. Bien que nous ne soyons pas satisfaits, il est évidemment beaucoup plus satisfaisant que le résultat de Node.js. Strong Curiosity Trend Voyons le code source du chrome pour voir comment il est implémenté. (https://chromium.googlesource.com/chromium/src.git/+/38.0.2125.101/base/time/time_win.cc)
Tout d'abord, Chromium utilise la fonction TimeGetTime () pour déterminer l'heure actuelle de la boucle. En regardant MSDN, vous pouvez constater que la précision de cette fonction est affectée par l'intervalle de temporisation actuel du système. Sur notre machine de test, il est théoriquement les 1,001 ms mentionnés ci-dessus. Cependant, par défaut, l'intervalle de minuterie est sa valeur maximale (15,625 ms sur la machine de test), sauf si l'application modifie l'intervalle de minuterie global.
Si vous suivez des nouvelles dans l'industrie informatique, vous devez avoir vu une telle nouvelle. Il semble que notre chrome ait réglé l'intervalle de minuterie très petit! Il semble que nous n'ayons pas à nous soucier de l'intervalle de minuterie du système? Ne soyez pas trop heureux trop tôt, une telle réparation nous a donné un coup. En fait, ce problème a été résolu dans Chrome 38. Faut-il utiliser la fixation du nœud-webkit précédent? Ce n'est évidemment pas assez élégant et nous empêche d'utiliser une version plus performante de Chromium.
En examinant plus loin le code source du chrome, nous pouvons constater que lorsqu'il y a un minuteur et le délai d'expiration du délai d'expiration <32 ms, le chrome modifiera l'intervalle de minuterie mondial du système pour atteindre une minuterie avec une précision inférieure à 15,625 ms. (Afficher le code source) Lors du démarrage de la minuterie, quelque chose appelé HighResolutionTimerManager sera activé. Cette classe appellera la fonction ActiveHighResolutionTimer en fonction du type d'alimentation du périphérique actuel. Plus précisément, lorsque le périphérique actuel utilise la batterie, il appellera ActiverHighResolutionTimer (FALSE) et True sera passé lors de l'utilisation de l'alimentation. La mise en œuvre de la fonction ActiverHighResolutionTimer est la suivante:
La copie de code est la suivante:
vide time :: enableHighResolutionTimer (bool activer) {
Base :: AutoLock Lock (g_high_res_lock.get ());
if (g_high_res_timer_enabled == activer)
retour;
g_high_res_timer_enabled = activer;
if (! g_high_res_timer_count)
retour;
// Puisque g_high_res_timer_count! = 0, un activateHighResolutionTimer (true)
// a été appelé ce qui appelé TimeBeginperiod avec g_high_res_timer_enabled
// avec une valeur qui est l'opposé de | Activer |. Avec cette information, nous
// Appelez TimeEndperiod avec la même valeur utilisée dans TimeBeginperiod et
// Donc annulez l'effet de période.
if (activer) {
TimeEndperiod (KmintimeRinterVallowResms);
TimeBeginperiod (KmintimeRintervalHighResms);
} autre {
TimeEndperiod (KmintimeRintervalHighResms);
TimeBeginPeriod (KMintimerInterVallowResms);
}
}
Où, KmintimerInterVallowResms = 4 et KmintimerIntervalHighResms = 1. TimeBeginperiod et Timeendperiod sont des fonctions fournies par Windows pour modifier l'intervalle de temporisation système. C'est-à-dire que lors de la connexion à l'alimentation, le plus petit intervalle de minuterie que nous pouvons obtenir est de 1 ms, tandis que lors de l'utilisation de la batterie, il est de 4 ms. Étant donné que notre boucle appelle en continu Settimeout, selon la spécification W3C, l'intervalle minimum est également 4 ms, donc je me sens soulagé, cela a peu d'impact sur nous.
Un autre problème de précision
De retour au début, nous avons constaté que les résultats des tests montrent que l'intervalle de Settimeout n'est pas stable à 4 ms, mais fluctue en continu. Le http://marks.lrednight.com/test.html#48 Les résultats des tests montrent également que les intervalles battent entre 48 ms et 49 ms. La raison en est que dans la boucle d'événement de Chromium et Node.js, la précision de l'appel de fonction Windows en attente de l'événement IO est affectée par la minuterie du système actuel. L'implémentation de la logique de jeu nécessite la fonction de requête Animation (mise à jour constamment du canevas), ce qui peut nous aider à définir l'intervalle de temporisation sur au moins KMintimerIntervallowResms (car il a besoin d'un temporisateur de 16 ms, ce qui déclenche l'exigence d'un temporisateur de haute précision). Lorsque la machine de test utilise la puissance, l'intervalle de minuterie du système est de 1 ms, donc le résultat du test a une erreur de ± 1 ms. Si votre ordinateur n'a pas changé l'intervalle de minuterie du système et exécute le test # 48 ci-dessus, Max peut atteindre 48 + 16 = 64 ms.
En utilisant l'implémentation Settimeout de Chromium, nous pouvons contrôler l'erreur de Settimeout (FN, 1) à environ 4 ms, tandis que l'erreur de Settimeout (FN, 48) peut contrôler l'erreur de Settimeout (FN, 48) à environ 1 ms. Nous avons donc un nouveau plan dans nos esprits, ce qui fait ressembler notre code à ceci:
La copie de code est la suivante:
/ * Obtenez la décoration d'intervalle maximale * /
Var Decoration = GetMaxInterValdeViation (BucketSize); // BucketSize = 48, déviation = 2;
fonction gameloop () {
var maintenant = date.Now ();
if (précédemmentbucket + bucketSize <= maintenant) {
PREMERBUCKET = MAINTENANT;
dologic ();
}
if (précédemmentBucket + BacketSize - Date.Now ()> Decoration) {
// attendez 46 ms. Le retard réel est inférieur à 48 ms.
setTimeout (Gameloop, BacketSize - Design);
} autre {
// occupé à attendre. Utilisez SetImMediate au lieu de Process.Nexttick car le premier ne bloque pas les événements IO.
SetImMediate (Gameloop);
}
}
Le code ci-dessus nous permet d'attendre un temps avec une erreur inférieure à Bucket_Size (Définition Bucket_Size) au lieu d'égaler directement un Bucket_Size. Même si l'erreur maximale se produit dans un retard de 46 ms, selon la théorie ci-dessus, l'intervalle réel est inférieur à 48 ms. Le reste du temps, nous utilisons la méthode d'attente occupée pour nous assurer que notre gameloop est exécuté dans un intervalle avec une précision suffisante.
Bien que nous ayons résolu le problème dans une certaine mesure avec le chrome, ce n'est évidemment pas assez élégant.
Rappelez-vous notre demande initiale? Notre code côté serveur devrait être en mesure d'exécuter directement sur un ordinateur avec un environnement Node.js sans le client Node-Webkit. Si vous exécutez directement le code ci-dessus, la valeur de la définition est d'au moins 16 ms, ce qui signifie que dans chaque 48 ms, nous devons attendre 16 ms. Le taux d'utilisation du processeur a augmenté.
Surprise inattendue
C'est tellement ennuyeux. Personne ne remarque un si gros bug dans Node.js? La réponse nous rend vraiment ravis. Ce bogue a été corrigé dans la version V.0.11.3. Vous pouvez également voir les résultats modifiés en affichant directement la branche maîtresse du code Libuv. L'approche spécifique consiste à ajouter un délai d'expiration à l'heure actuelle de la boucle une fois que la fonction de scrutin attendait l'achèvement. De cette façon, même si GetTickCount n'a pas réagi, nous avons quand même ajouté ce temps d'attente après l'attente du sondage. Ainsi, la minuterie peut expirer en douceur.
En d'autres termes, le problème qui a été travaillé dur depuis longtemps a été résolu dans V.0.11.3. Cependant, nos efforts n'étaient pas vains. Étant donné que même si la fonction GetTickCount est éliminée, la fonction de scrutin elle-même est affectée par le temporisateur du système. Une solution consiste à écrire le plugin Node.js pour modifier les intervalles des minuteries du système.
Cependant, les paramètres initiaux de notre jeu ne sont pas sans serveur. Une fois que le client a créé une pièce, il devient un serveur. Le code du serveur peut s'exécuter dans un environnement Node-Webkit, donc la priorité des problèmes de minuterie sur les systèmes Windows n'est pas le plus élevé. Suivant la solution que nous avons donnée ci-dessus, les résultats sont suffisants pour nous satisfaire.
fin
Après avoir résolu le problème du temporisateur, notre implémentation de cadre ne sera essentiellement plus entravée. Nous fournissons une prise en charge WebSocket (dans des environnements HTML5 purs) et personnalisons le protocole de communication pour obtenir une prise en charge de socket de performance plus élevée (dans les environnements Node-Webkit). Bien sûr, les fonctions de Spaceroom étaient relativement simples au début, mais à mesure que la demande a été proposée et que le temps a augmenté, nous améliorons progressivement ce cadre.
Par exemple, lorsque nous avons constaté que lorsque nous devons générer des nombres aléatoires cohérents dans notre jeu, nous avons ajouté cette fonction à Spaceeroom. Au début du jeu, Spacerooom distribuera des graines de nombre aléatoire. L'espace-toirome du client fournit une méthode pour utiliser le hasard de MD5 pour générer des nombres aléatoires à l'aide de graines de nombre aléatoire.
Cela semble assez soulagé. J'ai également beaucoup appris en train d'écrire un tel cadre. Si vous êtes intéressé par l'espace-toi, vous pouvez également y participer. Je crois que Spaceroom utilisera ses poings et ses pieds dans plus d'endroits.