{
   Double Commander
   -------------------------------------------------------------------------
   Executing functions in a thread.

   Copyright (C) 2009-2011 Przemysław Nagay (cobines@gmail.com)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
}
unit uFunctionThread;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, syncobjs;

type
  TFunctionThreadMethod = procedure(Params: Pointer) of object;

  PFunctionThreadItem = ^TFunctionThreadItem;
  TFunctionThreadItem = record
    Method: TFunctionThreadMethod;
    Params: Pointer;
  end;

  TFunctionThread = class(TThread)
  private
    FFunctionsToCall: TFPList;
    FWaitEvent: PRTLEvent;
    FLock: TCriticalSection;
    FFinished: Boolean;

  protected
    procedure Execute; override;

  public
    constructor Create(CreateSuspended: Boolean); reintroduce;
    destructor Destroy; override;

    procedure QueueFunction(AFunctionToCall: TFunctionThreadMethod; AParams: Pointer = nil);
    procedure Finish;

    class procedure Finalize(var AThread: TFunctionThread);

    property Finished: Boolean read FFinished;
  end;

implementation

uses
  LCLProc, uDebug, uExceptions
  {$IFDEF MSWINDOWS}
  , ActiveX
  {$ENDIF}
  ;

constructor TFunctionThread.Create(CreateSuspended: Boolean);
begin
  FWaitEvent := RTLEventCreate;
  FFunctionsToCall := TFPList.Create;
  FLock := TCriticalSection.Create;
  FFinished := False;
  FreeOnTerminate := False;

  inherited Create(CreateSuspended, DefaultStackSize);
end;

destructor TFunctionThread.Destroy;
var
  i: Integer;
begin
  RTLeventdestroy(FWaitEvent);

  FLock.Acquire;
  for i := 0 to FFunctionsToCall.Count - 1 do
    Dispose(PFunctionThreadItem(FFunctionsToCall[i]));
  FLock.Release;

  FreeAndNil(FFunctionsToCall);
  FreeAndNil(FLock);

  inherited Destroy;
end;

procedure TFunctionThread.QueueFunction(AFunctionToCall: TFunctionThreadMethod; AParams: Pointer);
var
  pItem: PFunctionThreadItem;
begin
  if (not Terminated) and Assigned(AFunctionToCall) then
  begin
    New(pItem);
    pItem^.Method := AFunctionToCall;
    pItem^.Params := AParams;

    FLock.Acquire;
    try
      FFunctionsToCall.Add(pItem);
    finally
      FLock.Release;
    end;

    RTLeventSetEvent(FWaitEvent);
  end;
end;

procedure TFunctionThread.Finish;
begin
  Terminate;
  RTLeventSetEvent(FWaitEvent);
end;

procedure TFunctionThread.Execute;
var
  pItem: PFunctionThreadItem;
begin
  {$IFDEF MSWINDOWS}
  CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
  {$ENDIF}

  try
    while (not Terminated) or (FFunctionsToCall.Count > 0) do
    begin
      RTLeventResetEvent(FWaitEvent);

      pItem := nil;

      FLock.Acquire;
      try
        if FFunctionsToCall.Count > 0 then
        begin
          pItem := PFunctionThreadItem(FFunctionsToCall[0]);
          FFunctionsToCall.Delete(0);
        end;
      finally
        FLock.Release;
      end;

      if Assigned(pItem) then
      begin
        try
          pItem^.Method(pItem^.Params);
          Dispose(pItem);
        except
          on e: Exception do
          begin
            Dispose(pItem);
            HandleException(e, Self);
          end;
        end;
      end
      else
      begin
        RTLeventWaitFor(FWaitEvent);
      end;
    end;
  finally
    {$IFDEF MSWINDOWS}
    CoUninitialize;
    {$ENDIF}
    FFinished := True;
  end;
end;

class procedure TFunctionThread.Finalize(var AThread: TFunctionThread);
begin
  AThread.Finish;
{$IF (fpc_version<2) or ((fpc_version=2) and (fpc_release<5))}
  If (MainThreadID=GetCurrentThreadID) then
    while not AThread.Finished do
      CheckSynchronize(100);
{$ENDIF}
  AThread.WaitFor;
  AThread.Free;
  AThread := nil;
end;

end.

