{x$DEFINE OPTIMIZACION_ALFARO}
unit uRobotOptimizadorMultiCore;

interface

uses
{$IFDEF WINDOWS}
  ipcthrd,
{$ELSE}
  uEmuladorWinIPC, uKeyDir,
{$ENDIF}
  uSalasDeJuegoDistr, xMatDefs, Classes, SysUtils;

var
  cntCalculando: integer;
{$IFDEF WINDOWS}
  nomSemCntCalculando, nomEvPasoCompleto, nombreEvHiloProximoPaso: string;
{$ELSE}
  nomSemCntCalculando, nomEvPasoCompleto, nombreEvHiloProximoPaso: string;
{$ENDIF}

type
  TRobotOptimizadorMultiCore = class(TThread)
  public
    sala: TSalaDeJuego;
    estrellaIni, estrellaFin: integer;
    darPasoAuto: boolean;
    iRobotEnLaLista: integer;

    constructor Create(sala: TSalaDeJuego; const costoFuturo: TMatOfNReal;
      darPasoAutomaticamente: boolean; iRobotEnLaLista: integer);
    procedure Execute; override;
    procedure liberarSala;
  end;

//TODO definir un criterio para cuantos hilos usar
function getNHilos(nCores, nEstrellas, nSorteos, nMaxIteraciones: integer): integer;

{$IFDEF LINUX}
procedure CrearArchiKey(s: string);
{$ENDIF}

implementation

function getNHilos(nCores, nEstrellas, nSorteos, nMaxIteraciones: integer): integer;
{$IFDEF OPTIMIZACION_ALFARO}
var
  nPuntosACalcular: integer;
{$ENDIF}
begin
{$IFNDEF OPTIMIZACION_ALFARO}
  Result := nCores;
{$ELSE}
  if nCores = 1 then
    Result := 1
  else
  begin
    nPuntosACalcular := nEstrellas * nSorteos * (nMaxIteraciones + 1) div 2;
    if nCores = 2 then
    begin
      if nPuntosACalcular <= 100 then
        Result := 2
      else if nPuntosACalcular <= 1000 then
        Result := 4
      else if nPuntosACalcular <= 5000 then
        Result := 8
      else
        Result := 16;
    end
    else
    begin
      if nPuntosACalcular <= 100 then
        Result := 4
      else if nPuntosACalcular <= 1000 then
        Result := 8
      else if nPuntosACalcular <= 5000 then
        Result := 16
      else
        Result := 32;
    end;
  end;
{$ENDIF}
end;

constructor TRobotOptimizadorMultiCore.Create(sala: TSalaDeJuego;
  const costoFuturo: TMatOfNReal; darPasoAutomaticamente: boolean;
  iRobotEnLaLista: integer);
begin
  inherited Create(True);
  //cargarSala se debe hacer secuencial
  self.sala := TSalaDeJuego.cargarSala(sala.archiSala, True);

  self.sala.globs.NMAX_ITERACIONESDELPASO_OPT := sala.globs.NMAX_ITERACIONESDELPASO_OPT;

  self.sala.estabilizarInicio := False;
  self.sala.inicializarOptimizacion(costoFuturo);
  self.iRobotEnLaLista := iRobotEnLaLista;
  self.darPasoAuto := darPasoAutomaticamente;
  self.FreeOnTerminate := False;
  self.Resume;
end;

procedure TRobotOptimizadorMultiCore.Execute;
var
  semCntCalculando: TMutex;
{$IFDEF WINDOWS}
  evPasoCompleto, evProximoPaso: TEvent;
{$ELSE}
  evPasoCompleto: TEventTX;
  evProximoPaso: TEventRX;
{$ENDIF}
begin
{$IFDEF WINDOWS}
  semCntCalculando := TMutex.Create(nomSemCntCalculando);
  evPasoCompleto := TEvent.Create(nomEvPasoCompleto, False);
  evProximoPaso := TEvent.Create(nombreEvHiloProximoPaso +
    IntToStr(iRobotEnLaLista), False);
{$ELSE}
  semCntCalculando := TMutex.Create(nomSemCntCalculando, 1);
  evPasoCompleto := TEventTX.Create(nomEvPasoCompleto, 1);
  evProximoPaso := TEventRX.Create(nombreEvHiloProximoPaso, iRobotEnLaLista);
{$ENDIF}

  if not semCntCalculando.Get(6000) then
  begin
    writeln('TRobotOptimizadorMultiCore.Execute: No obtuve semCntCalculando 1');
    Terminate;
  end
  else
  begin
    cntCalculando := cntCalculando - 1;
    if cntCalculando = 0 then
      evPasoCompleto.Signal;
    semCntCalculando.Release;
  end;

  while (sala.globs.kPaso_ > 0) and (not Terminated) do
  begin
    if not evProximoPaso.Wait(60000) then
    begin
      writeln('TRobotOptimizadorMultiCore.Execute: No me despertaron de evProximoPaso');
      Terminate;
      break;
    end;

    sala.calcularRangoEstrellas(estrellaIni, estrellaFin);
    if not semCntCalculando.Get(6000) then
    begin
      writeln('TRobotOptimizadorMultiCore.Execute: No obtuve semCntCalculando 2');
      Terminate;
      break;
    end;
    cntCalculando := cntCalculando - 1;
    if cntCalculando = 0 then
      evPasoCompleto.Signal;
    semCntCalculando.Release;

    if darPasoAuto then
      sala.darPaso;
  end;
  semCntCalculando.Free;
  evPasoCompleto.Free;
  evProximoPaso.Free;
  //Esto esta mal aca. Debería llamarse en el destructor de la clase, pero
  //FreeOnTerminate = true no llama al metodo Free si esta redefinido y el
  //evento OnTerminate en Free Pascal se llama después que el thread esta destruido
  //Si el Thread ejecuta bien no hay problema, pero sino hay que liberar la sala
  //a mano
  sala.Free;
  sala := nil;
end;

procedure TRobotOptimizadorMultiCore.liberarSala;
begin
  if sala <> nil then
  begin
    sala.Free;
    sala := nil;
  end;
end;

{$IFDEF LINUX}
procedure CrearArchiKey(s: string);
var
  f: textfile;
begin
  //writeln('Creando archivo key ' + s);
  ForceDirectories(ExtractFilePath(s));
  AssignFile(f, s);
  Rewrite(f);
  writeln(f, 'ArchiKey: ', s, ' - no borrar.');
  closefile(f);
end;

{$ENDIF}

initialization
{$IFDEF WINDOWS}
  nomSemCntCalculando := 'nomSemCntCalculando';
  nomEvPasoCompleto := 'nomEvPasoCompleto';
  nombreEvHiloProximoPaso := 'nombreEvHiloProximoPaso';
{$ELSE}
  nomSemCntCalculando := uKeyDir.keyBaseDir + '/nomSemCntCalculando';
  nomEvPasoCompleto := uKeyDir.keyBaseDir + '/nomEvPasoCompleto';
  nombreEvHiloProximoPaso := uKeyDir.keyBaseDir + '/nombreEvHiloProximoPaso';
{$ENDIF}

end.
