HeadlessClients

De Wiki 11th MEU
Saltar a: navegación, buscar


Introducción

Antes de nada, explicar que es un Headless Client (HC) o cliente sin cabeza. Como su nombre indica un HC es un cliente, que se conecta al servidor, pero no tiene interfaz.

Y para que necesitamos conectar un HC, pues porque Arma 3 tiene un pequeño gran problema y es que carga el juego solo en un núcleo de la CPU, es decir que desaprovecha el resto del procesador a la hora de ejecutarse. Esto se traduce en un problema de rendimiento del propio servidor, que funciona a menos fps.

El servidor de normal gestiona toda la IA y todos los objetos dentro de un mapa, incluyendo por ejemplo vehículos vacíos, y se encarga de que se sincronicen con los clientes. Si por ejemplo un cliente/jugador coge un vehículo, este transfiere la gestión de ese vehículo al jugador hasta el momento en que lo deje, sincronizándolo con los demás clientes, por eso a veces cuando el servidor tiene pocos fps no le da tiempo a sincronizar bien todas las unidades y nos da desinc entre los clientes.

El uso de los HCs es precisamente para que toda la gestión de la IA se transfiera del servidor, y por así decirlo se la quite, con esto conseguimos quitar parte de la carga que de normal solo soporta un núcleo de la CPU.

Para que todo esto funcione medianamente bien, el HC tiene que tener una buena latencia con respecto al servidor ya que sino la trasferencia de gestión de IA también tendría el lastre de esa misma latencia. Por eso de normal conectaremos esos HCs directamente en el propio servidor, aunque teóricamente podríamos conectarlos desde cualquier otro ordenador, eso sí, configurándolo.

Vale, todo esto suena bien, donde está la pega, evidentemente con todo esto que os he contado cuantos más HCs usemos pues mejor nos funcionara el servidor. El tema es que estamos limitados al Hardware del propio servidor, si por ejemplo en nuestro caso, tenemos un servidor de 8 núcleos y 16 gb de RAM podremos usar un número limitado de HCs. Dependiendo de la misión que tenga cargado el servidor, así como de los addons, estará consumiendo un núcleo y entre 1500 o 2000 mb de RAM, es decir, que si tenemos por ejemplo 4 HCs más el servidor, ya son 5 núcleos y entre 7500 o 10000 mb de Ram solo ese servidor, el resto estaría para los demás servidores arrancados y para la web. Por lo tanto, es muy probable que tengamos que parar los otros servidores. Y por lo que no es aconsejable dejar los HCS funcionando de continuo.

Requisitos

Servidor

Bien para que todo esto funcione lo primero de todo es en la configuración del server indicarle las IPs que pueden conectarse a él como HC:

En la cfg del servidor:

headlessClients[]={"127.0.0.1"};

localClient[]={127.0.0.1};

Con esto le damos permiso a que se conecten todos los clientes del propio servidor, en caso de ser querer que se conecte un HCs externo pues habría que poner la IP.

HeadlessClients

Para arráncalos en Linux deberíamos ejecutar el siguiente comando:

En la ruta del servidor:

./arma3server -client -connect=127.0.0.1:Puerto del server -password="Pass"  -nosound -profiles="Nombre HC" -name="Nombre HC" -mod="@addon1;@addon2;@addon3;" 

Por ejemplo:

cd /home/servidores/arma3/cooperativo

./arma3server -client -connect=127.0.0.1:2302 -password="Pass del servidor"  -nosound -profiles="hc1" -name="hc1" -mod="@cba_a3;@ace;@ace_compat_rhs;@acex;@acre2;@meu;@meu_rhs;@meu_fleet;@meu_maps;
@cup_terrains;@cup_units;@cup_vehicles;@cup_weapons;@rhsafrf;@rhsusaf;@rhsgref;@nimitz;" 

No obstante, he dejado unos pequeños scripts para arrancarlos rápidamente, aunque lo suyo seria tenerlos como servicios.

Recordar que son clientes no necesitan el @meu_server


Misión

Lo primero de todo es poner los HCs. Importante que se llamen hc1 hc2 hc3 … etc para que coincidan con los nombres indicados al ejecutar los HCs y que sean unidades jugables.

Hc1.png
 :
Hc2.png

A la hora de conectarse al servidor, solo el Admin los podrá ver en la lista de Slots.

Edición

ACEX

Bueno de esto me he enterado hace más bien poco, pero es lo más sencillo, y curiosamente ya lo teníamos configurado. Con el ACEX viene una opción para los headless client al activarla, cosa que como he dicho ya tenemos en las CBA_Settigns el servidor va a comprobar los HCs que tenga disponibles al inicio de la partida y durante esta va a estar dividiendo la carga entre todos automáticamente en caso de que uno de los HCs se caiga durante la partida la carga de este pasara al servidor y posteriormente volverá a equilibrar entre los restantes HCs. Según los del ACE este proceso lo hace bien con hasta 3 HCs asi que de normal usaremos 3.

Da una opción el editor en las unidades para añadirlas a la lista negra del headlessclient, eso básicamente es para forzar que no lo usen los hcs, por ejemplo algo que queremos o necesitemos que sea solo del servidor, de normal lo que nos interesa es todo lo contrario asi que no habilitaremos esa opción.

Este proceso es el más cómodo y el que recomiendo y ahora explico un poco los demás.

Alive

No es algo que controle, pero a la práctica es algo muy parecido a lo que hace el ACEX, el Alive gestiona automáticamente la IA repartiéndola entre los HCs que tenga disponibles cosa más que necesaria porque esta todo el rato respawneando y quitando IA

IA del Editor

Esto es algo que directamente no he usado, pero resumiendo, consiste en poner las IAS en el editor, guardar sin binarizar y luego buscar esas IAs dentro del sqm de la misión y luego crear un script para que la genere el HC. Esta más explicado en el PDF que dejare al final, pero lo veo un proceso demasiado complejo. Y es algo que no tiene mucho sentido de usar al tener por defecto configurado lo del ACEX

Transferir IA al iniciar la misión

Básicamente de esta manera lo que hace es poner un script al inicio de la misión que traspasa toda la gestión de la IA del servidor a los clientes nada más empezar. Es algo cómodo ya que es poner un script y llamarlo desde el INIT, aunque tampoco lo recomiendo dado que por defecto usamos lo del ACEX. También esta explicado más en detalle en el documento del final.

IA Spawneada

Esto es lo que he estado usando yo hasta ahora, simplificándolo, de esta manera spawneas directamente en el HC la IA que quieres por medio de scripts. Eso sí, por tema de rendimiento, es mejor el uso de funciones definidas en el INIT y llamadas después, osea el uso de Call en vez de execVM. Tampoco quiero enrollarme mucho con esto, porque hay un mundo, os dejo un pequeño ejemplo de una función que spawnea el número de unidades que quieras:

En el init.sqf defino las funciones:

creargrupo = compile (preprocessFileLineNumbers "scripts\creargrupo.sqf");

spawner = compile (preprocessFileLineNumbers "scripts\spawner.sqf"); 

La función spawner ejecuta un script al headless client que queramos en caso de que no este, lo ejecuta el servidor.

//Spawner, pasar el nombre del headlessclient y el script de spawn
//ejemplo ["HC1", marines1]call spawner;

private["_hc","_script"];
_hc = _this select 0;
_script = _this select 1;

_hcpresent = if(isNil _hc) then{False} else{True};
if(_hcpresent && isMultiplayer) then{
	if(!isServer && !hasInterface && (name player == _hc)) then{
		[] call _script;
	};
}else{	if(isServer) then{
		[] call _script;
	};
};

La funcion creargrupo.sqf

/*CrearGrupo 
Llamada:
	[numero, tipo, posicion,bando] call creargrupo;
*numero= numero de unidades dentro del grupo
*tipo= array de classnames
*posicion= posicion de respawn
*bando= faccion del grupo;  https://community.bistudio.com/wiki/Side

Devuelve:
Grupo
*/

//PARAMETROS RECOGIDOS
private["_num","_tipo","_pos","_band"];
_num=_this select 0;
_tipo = _this select 1;
_pos = _this select 2;
_band = _this select 3;

//INICIO DE VARIABLES
_camo=[];
_camo=_tipo;

//PREPARACION DE REPETICION DE ARRAY SI EL NUMERO DE UNIDADES ES ALTO
_conts=(count _camo);
_veces=(_num/_conts) - ((_num/_conts)%1);
if (_veces>0) then {
	for "_j" from 0 to (_veces -1)do {
		_camo=_camo+_camo;
	};
};
//CREACION DE GRUPO
_grupo = createGroup _band;

for "_i" from 0 to (_num -1) do {
	(_camo select _i) createUnit [position _pos, _grupo];
};

{
	_x setSkill ["aimingAccuracy", 0.30];
	_x setSkill ["aimingShake", 0.50];
	_x setSkill ["aimingSpeed", 0.4];
	_x setSkill ["endurance", 0.50];
	_x setSkill ["spotDistance", 0.35];
	_x setSkill ["spotTime", 0.35];
	_x setSkill ["courage", 0.75];
	_x setSkill ["reloadSpeed", 0.75];
	_x setSkill ["commanding", 0.75];
	_x setSkill ["general", 0.75];
	
} foreach units _grupo;

//FORMACION INICIAL
_wp0= _tripulacion addWaypoint [position _pos, 0, 0];
_wp0 setWaypointType "MOVE";
_wp0 setWaypointSpeed "FULL"; 
_wp0 setWaypointBehaviour "AWARE"; 
_wp0 setWaypointFormation "LINE";

//DEVUELVE
_grupo;

En un activador llamo a la función y hago que lo ejecute un HC, importante usar el remoteExec para que lo ejecute de manera global.

 ["hc1",([4,["fow_s_ija_f_type99_asst","fow_s_ija_f_type99_asst","fow_s_ija_f_type99_asst","fow_s_ija_f_type99_gunner","fow_s_ija_f_nco","fow_s_ija_f_rifleman",
 "fow_s_ija_f_rifleman","fow_s_ija_f_rifleman","fow_s_ija_f_rifleman","fow_s_ija_f_rifleman"], p1,west] call creargrupo; )] remoteExec ["spawner",0]; 

Un poco lio, no? xD, lo explico un poco:

Lo de arriba se resumiría asi:

[parámetros] remoteExec ["spawner”, 0];

Siendo los parámetros, los de la función spawner, y 0 porque se va a ejecutar de manera global, es decir lo ejecutaran tanto clientes como el servidor

Parámetros:

["hc1",(script a ejecutar)] 

Pues aquí es donde le digo que me lo ejecute el hc1

Script a ejecutar aquí le meto la otra función que es la que spawnea las unidades:

([4,["fow_s_ija_f_type99_asst","fow_s_ija_f_type99_asst","fow_s_ija_f_type99_asst","fow_s_ija_f_type99_gunner","fow_s_ija_f_nco","fow_s_ija_f_rifleman", 
"fow_s_ija_f_rifleman","fow_s_ija_f_rifleman","fow_s_ija_f_rifleman","fow_s_ija_f_rifleman"], p1,west] call creargrupo; )

Se resume en

[número de ias, array de classnames, posición de spawneo, bando del grupo] call creargrupo;

En el ejemplo spawneo 4 ias que spawnearan en la posición de la lógica del juego llamada p1 y serán bluefor, y los classnames de esas cuatro ias serán "fow_s_ija_f_type99_asst", "fow_s_ija_f_type99_asst","fow_s_ija_f_type99_asst","fow_s_ija_f_type99_gunner".

Si en vez de ser 4 son 54, repetirá los classnames de los 54 según la lista/array pasada en la función.


El tema de respawn de unidades por script da para un mundo, y se pueden hacer bastantes cosillas lo que hay que tener claro, es quien ejecuta los scripts, si pones que algo sea global lo ejecutaran todos, así que tendrás que limitarlo con IFs para que solo lo ejecuten unos u otros.

Véase también