Análisis del código fuente de Skynet del temporizador skynet_timer.c

Skynet tiene su propia función de temporizador skynet-src / skynet_timer.c, cuando se inicia skynet, se creará un hilo para ejecutar el temporizador. Llame a skynet_updatetime () cada cuadro (0.0025 segundos / cuadro)

 1 // skynet-src/skynet_start.c
 2 
 3 create_thread(&pid[1], thread_timer, m);
 4 
 5 static void *
 6 thread_timer(void *p) {
 7     struct monitor * m = p;
 8     skynet_initthread(THREAD_TIMER);
 9     for (;;) {
10         skynet_updatetime();
11         CHECK_ABORT
12         wakeup(m,m->count-1);
13         usleep(2500);  //2500微妙=0.0025秒
14         if (SIG) {
15             signal_hup();
16             SIG = 0;
17         }
18     }
19     ...
20 }

1. Ideas de diseño

La idea de diseño de skynet se refiere al mecanismo del temporizador dinámico del kernel de Linux y se refiere a la introducción del temporizador dinámico del kernel de Linux http://www.cnblogs.com/leaven/archive/2010/08/19/1803382.html ,

 

En skynet, la precisión del tiempo es de 0.01 segundos, que es suficiente para el servidor del juego. La definición es 1 tick = 0.01 segundo y 1 segundo = 100 ticks. La idea central es: cada temporizador establece un número caducado de tics y el número de ticks del sistema actual (0 al inicio, luego 1 tick 1 tick y luego retroceder) para comparar la diferencia, si el intervalo de diferencia es relativamente pequeño (0 <= intervalo <= 2 ^ 8-1), lo que significa que el temporizador está llegando y debe seguirse de cerca. Guárdelos en la lista enlazada del temporizador 2 ^ 8; si el intervalo es mayor, el temporizador está más lejos , por lo que no necesita prestar demasiada atención., Dividido en 4 niveles, 2 ^ 8 <= intervalo <= 2 ^ (8 + 6) -1, 2 ^ (8 + 6) <= intervalo <= 2 ^ (8 + 6 + 6), ..., Cada nivel solo necesita 2 ^ 6 listas vinculadas con temporizador para guardar. Por ejemplo, para un temporizador con 2 ^ 8 <= intervalo <= 2 ^ (8 + 6) -1 , guarde el mismo valor idx del intervalo >> 8 en el primer En una lista enlazada cuya posición de rango es idx.

La ventaja de esto es que en lugar de crear una lista vinculada para cada intervalo, solo se necesitan 2 ^ 8 + 4 * (2 ^ 6) listas vinculadas, lo que ahorra mucho memoria.

Después de eso, en diferentes situaciones, se asignan diferentes niveles de temporizadores. Cuanto más alto es el nivel, más distante está y menos veces hay que reasignarlo.

Para los amigos que se dedican al desarrollo de clientes de juegos, el desarrollo de la interfaz es muy engorroso, la tecnología del cliente se actualiza muy rápidamente y rara vez pueden contactar con el desarrollo subyacente, los amigos que quieren transformarse para ser desarrolladores de servidores de juegos, aprenden skynet para comprender el conjunto Arquitectura del proceso y componentes básicos del servidor Y la programación pensando en el servidor.

Para el desarrollo de juegos pequeños, skynet es un marco de servidor de juegos liviano, mientras aprende el lado del servidor, el desarrollo de pila completa

Acerca de Skynet, aquí hay un video de aprendizaje para todos, que explica los siguientes puntos de conocimiento en detalle, el enlace del video: https://ke.qq.com/course/2806743?flowToken=1030833

 

2. Análisis del código fuente

Estructura de datos: temporizador-> cerca, guardar 2 ^ 8 próximas listas vinculadas de temporizador; temporizador-> t, guardar 4 matrices jerárquicas, cada elemento de la matriz es una lista vinculada; temporizador-> ahorro de tiempo desde el inicio de skynet hasta ahora Número de ticks

 1 // skynet-src/skynet_timer.c
 2 struct timer_event {
 3     uint32_t handle;
 4     int session;
 5 };
 6 
 7 struct timer_node { //单个定时器节点
 8     struct timer_node *next;
 9     uint32_t expire; //到期滴答数
10 };
11 
12 struct link_list { //定时器链表
13     struct timer_node head;
14     struct timer_node *tail;
15 };
16 
17 struct timer {
18     struct link_list near[TIME_NEAR];
19     struct link_list t[4][TIME_LEVEL]; 
20     struct spinlock lock;
21     uint32_t time; //启动到现在走过的滴答数,等同于current
22     ...
23 };

Llame a skynet_timeout para crear un temporizador

Luego, llame a timer_add (línea 8) para asignar espacio para el puntero de timer_node. No hay un campo timer_event en la estructura timer_node. Además del tamaño del nodo en sí, se asigna un espacio adicional de timer_event size para almacenar el evento, y luego el se puede usar la ubicación del nodo + 1 Obtener datos de timer_event

Luego, llame a add_node (línea 21) y agréguelo a la lista del temporizador. Si el conteo de ticks expirado del temporizador está cerca del actual (<2 ^ 8), significa que el temporizador de activación se agregará a la matriz T-> near , de lo contrario sumar al correspondiente T-> T [i] según la diferencia

 1 // skynet-src/skynet_timer.c
 2 int
 3 skynet_timeout(uint32_t handle, int time, int session) {
 4     ...
 5     struct timer_event event;
 6     event.handle = handle;
 7     event.session = session;
 8     timer_add(TI, &event, sizeof(event), time);
 9 
10     return session;
11 }
12 
13 static void
14 timer_add(struct timer *T,void *arg,size_t sz,int time) {
15     struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node)+sz);
16     memcpy(node+1,arg,sz);
17 
18     SPIN_LOCK(T);
19 
20     node->expire=time+T->time;
21     add_node(T,node);
22 
23     SPIN_UNLOCK(T);
24 }
25 
26 static void
27 add_node(struct timer *T,struct timer_node *node) {
28     uint32_t time=node->expire;
29     uint32_t current_time=T->time;
30         
31     if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) {
32         link(&T->near[time&TIME_NEAR_MASK],node);
33     } else {
34         int i;
35         uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;
36         for (i=0;i<3;i++) {
37             if ((time|(mask-1))==(current_time|(mask-1))) {
38                  break;
39             }
40             mask <<= TIME_LEVEL_SHIFT;
41         }
42 
43         link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);        
44     }
45 }

Cada cuadro activa la lista vinculada del temporizador caducado desde T-> near, y el número de ticks caducados para todos los nodos en la lista vinculada de cada elemento en la matriz cercana es el mismo.

Llame a dispatch_list para distribuir, obtenga los datos de timer_event a través de current + 1 (línea 18), y luego envíe un mensaje a event-> handle para activar el temporizador (línea 25)

 1 // skynet-src/skynet_timer.c
 2 static inline void
 3 timer_execute(struct timer *T) {
 4     int idx = T->time & TIME_NEAR_MASK;
 5         
 6     while (T->near[idx].head.next) {
 7         struct timer_node *current = link_clear(&T->near[idx]);
 8         SPIN_UNLOCK(T);
 9         // dispatch_list don't need lock T
10         dispatch_list(current);
11         SPIN_LOCK(T);
12     }
13 }
14 
15 static inline void
16 dispatch_list(struct timer_node *current) {
17     do {
18         struct timer_event * event = (struct timer_event *)(current+1);
19         struct skynet_message message;
20         message.source = 0;
21         message.session = event->session;
22         message.data = NULL;
23         message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;
24 
25        skynet_context_push(event->handle, &message);
26                
27        struct timer_node * temp = current;
28        current=current->next;
29        skynet_free(temp);      
30     } while (current);
31 }

Además de activar el temporizador en cada cuadro, también es necesario reasignar el intervalo (timer_shift) donde se encuentra el temporizador. Debido a que el temporizador que se activará se almacena en T-> near, solo hay un número de ticks según TIME_NEAR-1 (2 ^ 8-1) Es posible que deba asignarse (línea 22). De lo contrario, asigne un cierto nivel en T-> t.

Cuando el octavo bit de T-> tiempo no es todo 0, no hay necesidad de asignar, por lo que cada 2 ^ 8 ticks deben asignarse una vez;

Cuando los bits del 9 al 14 de T-> tiempo no son todos 0, el nivel T [0] se reasigna y cada 2 ^ 8 tics se asignan una vez, idx comienza desde 1 y se asigna +1 cada vez;

Cuando los dígitos 15 al 20 de T-> tiempo no son todos 0, reasigne el nivel T [1], una vez cada 2 ^ (8 + 6) tics, idx comienza desde 1 y se asigna +1 cada vez;

Cuando los bits 21-26 de T-> tiempo no son todos 0, el nivel T [2] se reasigna, cada 2 ^ (8 + 6 + 6) ticks se asignan una vez, idx comienza desde 1 y +1 se asigna cada vez ;

Cuando los bits 27 a 32 de T-> tiempo no son todos 0, el nivel T [3] se reasigna, y cada 2 ^ (8 + 6 + 6 + 6) ticks se asignan una vez, idx comienza desde 1, y cada asignación +1;

Es decir, cuanto más alto es el nivel del temporizador, más lejos, menos preocupado está y menos veces necesita ser reasignado.

 1 // skynet-src/skynet_timer.c
 2 static void
 3 move_list(struct timer *T, int level, int idx) {
 4     struct timer_node *current = link_clear(&T->t[level][idx]);
 5     while (current) {
 6         struct timer_node *temp=current->next;
 7         add_node(T,current);
 8         current=temp;
 9     }
10 }
11 
12 static void
13 timer_shift(struct timer *T) {
14     int mask = TIME_NEAR;
15     uint32_t ct = ++T->time;
16     if (ct == 0) {
17         move_list(T, 3, 0);
18     } else {
19         uint32_t time = ct >> TIME_NEAR_SHIFT;
20         int i=0;
21 
22         while ((ct & (mask-1))==0) {
23             int idx=time & TIME_LEVEL_MASK;
24             if (idx!=0) {
25                 move_list(T, i, idx);
26                 break;                          
27             }
28             mask <<= TIME_LEVEL_SHIFT;
29             time >>= TIME_LEVEL_SHIFT;
30             ++i;
31         }
32     }
33 }

3. Cómo utilizar

Cree un temporizador en la capa C a través de skynet_timeout

Cree un temporizador en la capa Lua a través de skynet.timeout, por ejemplo, skynet.timeout (200, f), que activa la función de devolución de llamada f después de 200 ticks (2 segundos).

 

 

Supongo que te gusta

Origin blog.csdn.net/Linuxhus/article/details/112032698
Recomendado
Clasificación