Prólogo: Para empezar, hablaré sobre el proyecto para que haya ideas sobre cómo trabajamos en el proyecto y para recrear el dolor que sentimos.
Como desarrollador, ingresé al proyecto en 2015-2016, no lo recuerdo exactamente, pero funcionó 2-3 años antes. El proyecto fue muy popular en su campo, a saber, los servidores de juegos. Qué extraño no sonaba, pero los proyectos en los servidores de juegos están en curso hasta el día de hoy, recientemente vi vacantes y trabajé un poco en el mismo equipo. Dado que los servidores de juegos se basan en un juego ya creado, por lo tanto, se utiliza un lenguaje de secuencias de comandos para el desarrollo integrado en el motor del juego.
Estamos desarrollando casi desde cero un proyecto sobre Garry's Mod (Gmod), es importante tener en cuenta que, al momento de escribir este artículo, Harry ya está creando un nuevo proyecto de S & Box en Unreal Engine. Todavía estamos sentados en la Fuente.
Que generalmente no es adecuado para nuestro tema de servidor.

"¿Por qué da miedo tu historia?" - usted pregunta
Tenemos un tema fuerte para el servidor de juegos, a saber, "Stalker" e incluso con elementos de juegos de rol (RP), la pregunta surge de inmediato: "¿Cómo pueden todos implementar esto en un servidor?".
Teniendo en cuenta que el motor de Source es antiguo (la versión 2013 también se usa en Gmod de 32 bits), no harás tarjetas grandes, hay pequeñas restricciones en el número de Entity, Mesh y mucho más.
Quien trabajó en el motor lo entenderá.
Resulta que la tarea es generalmente imposible, hacer un acosador limpio multijugador con misiones, elementos RPG del propio original y preferiblemente una pequeña trama.
En primer lugar, la ortografía inicial fue difícil (muchas acciones de la categoría: tirar un objeto, levantar un objeto se escribieron desde cero), con la esperanza de que sería más fácil continuar, pero los requisitos aumentaron. La mecánica del juego estaba lista, todo lo que quedaba era hacer inteligencia, actualizar y todo tipo de cosas. En general, todos se transfirieron como pudieron.

Los problemas comenzaron ya durante el trabajo de la primera versión de lanzamiento, a saber (retrasos, retrasos en el servidor).
Parece que un servidor poderoso podría procesar con calma las solicitudes y mantener todo el Modo de juego.
Descripción simple del modo de juegoEste es el nombre de un conjunto de scripts escritos para describir la mecánica del servidor.
Por ejemplo: queremos el tema de las ahora populares "Royal Battles", lo que significa que el nombre debe corresponder también a la mecánica del juego. "La generación de jugadores en el avión, puedes recoger cosas, los jugadores pueden comunicarse, no puedes usar más de 1 casco, etc." - Todo esto es descrito por la mecánica del juego en el servidor.
Los retrasos estaban en el lado del servidor debido a la gran cantidad de jugadores, ya que un jugador consume mucha RAM de aproximadamente 80-120 mb (sin contar elementos en el inventario, habilidades, etc.), y en el lado del cliente hubo una fuerte disminución FPS
La potencia de la CPU no era suficiente para procesar la física, era necesario usar menos objetos con propiedades físicas.
También, además, estaban nuestros guiones escritos por uno mismo que no estaban optimizados en absoluto.

Primero, por supuesto, leemos artículos sobre optimización en Lua. Incluso se
suicidó que querían escribir archivos DLL en C ++, pero el problema surgió al descargar el archivo DLL del servidor a los clientes. Usando C ++ para una DLL, puede escribir un programa que intercepte silenciosamente los datos; los desarrolladores de Gmod agregaron una extensión a las excepciones para la descarga por parte de los clientes (seguridad, aunque en realidad nunca sucedió). Aunque sería conveniente y Gmod se volvería más flexible, pero más peligroso.
Luego, miramos el generador de perfiles (afortunadamente, personas inteligentes lo escribieron) y hubo un horror en las funciones, se notó que inicialmente había funciones muy lentas en la biblioteca del motor Gmod.
Si trataste de escribir en Gmod, entonces sabes que hay una biblioteca incorporada llamada matemática.
Y las funciones más lentas en él son, por supuesto, matemáticas, abrazadera y matemáticas.
Hurgando en el código de personas, se notó que las funciones se lanzaban en diferentes direcciones, se usaba en casi todas partes, ¡pero de manera incorrecta!
Vamos a practicar. Por ejemplo, queremos redondear las coordenadas del vector de posición para mover la entidad (por ejemplo, un jugador).
local x = 12.5 local y = 14.9122133 local z = 12.111 LocalPlayer():SetPos( Vector( Math.Round(x), Math.Round(y), Math.Round(z) )
3 funciones de redondeo complejas, pero nada serio, a menos, por supuesto, en un bucle y no se use con frecuencia, pero la abrazadera es aún más difícil.
El siguiente código se usa a menudo en proyectos y nadie quiere cambiar nada.
self:setLocalVar("hunger", math.Clamp(current + 1, 0, 100))
Por ejemplo, self apunta al objeto de un jugador y tiene una variable local que inventamos, que cuando se restablece el servidor, matemática. Clamp esencialmente como un bucle hace una asignación suave, les gusta hacer una interfaz suave en Clamp.
Los problemas surgen cuando funciona en cada jugador que visita el servidor. Es un caso raro, pero si 5-15 (depende inmediatamente de la configuración del servidor) ingresa al servidor en un punto en el tiempo y esta función pequeña y simple comienza a funcionar para todos, entonces el servidor tendrá buenos retrasos en la CPU. Peor aún si las matemáticas. La abrazadera está en un bucle.
La optimización es realmente muy simple, localizas funciones con mucha carga. Parece primitivo, pero en 3 modos de juego y muchos complementos vi este código lento.
Si necesita obtener el valor y usarlo en el futuro, no necesita volver a obtenerlo si no cambia. Después de todo, un jugador que ingrese al servidor en cualquier caso recibirá un hambre igual a 100, por lo que este código es muchas veces más rápido.
local value = math.Clamp(current + 1, 0, 100) self:setLocalVar("hunger", value)
Todo está bien, comenzaron a mirar más a fondo cómo funciona. Como resultado, comenzamos una manía para optimizar todo.
Notamos que el estándar para el loop es lento y decidimos crear nuestra propia bicicleta, que será más rápida (no nos olvidamos del blackjack) y luego comenzó el juego.

SpoilerIncluso logramos hacer el ciclo más rápido en Lua Gmod, pero con la condición de que debería haber más de 100 elementos.
A juzgar por el tiempo dedicado a nuestro ciclo y su uso en el código, intentamos en vano hacerlo porque encontró aplicación solo en el engendro en el mapa de anomalías después de arrojarlas y eliminarlas.
Y así al código. Por ejemplo, necesita encontrar todas las entidades con un nombre al comienzo de anom, tenemos anomalías en este nombre de clase.
Aquí está el script normal para Lua Gmod:
local anomtable = ents.FindByClass("anom_*") for k, v in pairs(anomtable) do v:Remove() end
Aquí está para el fumador:
Es obvio de inmediato que dicho código
g * obviamente será más lento que el estándar "en pares", pero resultó que no.
local b, key = ents.FindByClass("anom_*"), nil repeat key = next(b, key) b[key]:Remove() until key != nil
Para analizar completamente estas opciones de bucle, debe traducirlas a un script Lua normal.
Por ejemplo, anomtable tendrá 5 elementos.
La eliminación se reemplaza por la adición habitual. Lo principal es ver la diferencia en la cantidad de instrucciones entre las dos opciones para implementar el bucle for.
Ciclo de vainilla:
local anomtable = { 1, 2, 3, 4, 5 } for k, v in pairs(anomtable) do v = v + 1 end
El nuestro es genial:
local b, key = { 1, 2, 3, 4, 5 }, nil repeat key = next(b, key) b[key] = b[key] + 1 until key ~= nil
Veamos el código del intérprete (
como un ensamblador, no se recomienda mirar bajo un spoiler como un programador de alto nivel ).
Por si acaso, quite los jones de las pantallas. Le advertíDesmontador de ciclo de vainilla ; Name: for1.lua ; Defined at line: 0 ; #Upvalues: 0 ; #Parameters: 0 ; Is_vararg: 2 ; Max Stack Size: 7 1 [-]: NEWTABLE R0 5 0 ; R0 := {} 2 [-]: LOADK R1 K0 ; R1 := 1 3 [-]: LOADK R2 K1 ; R2 := 2 4 [-]: LOADK R3 K2 ; R3 := 3 5 [-]: LOADK R4 K3 ; R4 := 4 6 [-]: LOADK R5 K4 ; R5 := 5 7 [-]: SETLIST R0 5 1 ; R0[(1-1)*FPF+i] := R(0+i), 1 <= i <= 5 8 [-]: GETGLOBAL R1 K5 ; R1 := pairs 9 [-]: MOVE R2 R0 ; R2 := R0 10 [-]: CALL R1 2 4 ; R1,R2,R3 := R1(R2) 11 [-]: JMP 13 ; PC := 13 12 [-]: ADD R5 R5 K0 ; R5 := R5 + 1 13 [-]: TFORLOOP R1 2 ; R4,R5 := R1(R2,R3); if R4 ~= nil then begin PC = 12; R3 := R4 end 14 [-]: JMP 12 ; PC := 12 15 [-]: RETURN R0 1 ; return
Desmontador de bicicletas ; Name: for2.lua ; Defined at line: 0 ; #Upvalues: 0 ; #Parameters: 0 ; Is_vararg: 2 ; Max Stack Size: 6 1 [-]: NEWTABLE R0 5 0 ; R0 := {} 2 [-]: LOADK R1 K0 ; R1 := 1 3 [-]: LOADK R2 K1 ; R2 := 2 4 [-]: LOADK R3 K2 ; R3 := 3 5 [-]: LOADK R4 K3 ; R4 := 4 6 [-]: LOADK R5 K4 ; R5 := 5 7 [-]: SETLIST R0 5 1 ; R0[(1-1)*FPF+i] := R(0+i), 1 <= i <= 5 8 [-]: LOADNIL R1 R1 ; R1 := nil 9 [-]: GETGLOBAL R2 K5 ; R2 := next 10 [-]: MOVE R3 R0 ; R3 := R0 11 [-]: MOVE R4 R1 ; R4 := R1 12 [-]: CALL R2 3 2 ; R2 := R2(R3,R4) 13 [-]: MOVE R1 R2 ; R1 := R2 14 [-]: GETTABLE R2 R0 R1 ; R2 := R0[R1] 15 [-]: ADD R2 R2 K0 ; R2 := R2 + 1 16 [-]: SETTABLE R0 R1 R2 ; R0[R1] := R2 17 [-]: EQ 1 R1 K6 ; if R1 == nil then PC := 9 18 [-]: JMP 9 ; PC := 9 19 [-]: RETURN R0 1 ; return
Una persona sin experiencia simplemente dirá que un ciclo regular es más rápido porque hay menos instrucciones (15 vs. 19).
Pero no debemos olvidar que cada instrucción en el intérprete tiene ciclos de procesador.
A juzgar por el código del desensamblador en el primer bucle, hay una instrucción forloop escrita de antemano para trabajar con la matriz, la matriz se carga en la memoria, se vuelve global, saltamos los elementos y agregamos una constante.
En la segunda variante, el método es diferente, que se basa más en la memoria, recibe una tabla, cambia un elemento, establece una tabla, comprueba cero y vuelve a llamar.
Nuestro segundo ciclo es rápido porque hay demasiadas condiciones y acciones en una instrucción (R4, R5: = R1 (R2, R3); si R4 ~ = nulo, entonces comience PC = 12; R3: = R4 final) debido a esto come mucho
, consume ciclos de reloj de la CPU para la ejecución, el pasado está nuevamente más ligado a la memoria.
La instrucción para bucle con una gran cantidad de elementos se rinde a nuestro ciclo sobre la velocidad de paso de todos los elementos. Esto se debe al hecho de que el direccionamiento directo a la dirección es más rápido, menos que cualquier beneficio de los pares. (Y no tenemos negación)
En general, en secreto, cualquier uso de negación en el código lo ralentiza; esto ya ha sido probado por pruebas y tiempo. La lógica negativa funcionará más lentamente ya que la ALU del procesador tiene un "inversor" de unidad de cómputo separado, para el operando unario (¡no!) Para trabajar, necesita acceder al inversor y esto tomará más tiempo.
Conclusión: todo lo estándar no siempre es mejor, sus bicicletas pueden ser útiles, pero nuevamente en un proyecto real, no debe idearlas si la velocidad de lanzamiento es importante para usted. Como resultado, hemos completado el desarrollo completo desde 2014 hasta la actualidad, una especie de otra "espera". Aunque parece un servidor de juegos normal que se instala en 1 día y está completamente configurado para el juego en 2 días, debe poder introducir algo nuevo.
Este proyecto a largo plazo todavía vio la segunda versión de sí mismo donde hay muchas optimizaciones en el código, pero hablaré sobre otras optimizaciones en los siguientes artículos. Apoyo con crítica o comentario, correcto si me equivoco.