// ************************************************************************
// ***************************** CEF4Delphi *******************************
// ************************************************************************
//
// CEF4Delphi is based on DCEF3 which uses CEF to embed a chromium-based
// browser in Delphi applications.
//
// The original license of DCEF3 still applies to CEF4Delphi.
//
// For more information about CEF4Delphi visit :
//         https://www.briskbard.com/index.php?lang=en&pageid=cef
//
//        Copyright © 2021 Salvador Diaz Fau. All rights reserved.
//
// ************************************************************************
// ************ vvvv Original license and comments below vvvv *************
// ************************************************************************
(*
 *                       Delphi Chromium Embedded 3
 *
 * Usage allowed under the restrictions of the Lesser GNU General Public License
 * or alternatively the restrictions of the Mozilla Public License 1.1
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * Unit owner : Henri Gourvest <hgourvest@gmail.com>
 * Web site   : http://www.progdigy.com
 * Repository : http://code.google.com/p/delphichromiumembedded/
 * Group      : http://groups.google.com/group/delphichromiumembedded
 *
 * Embarcadero Technologies, Inc is not permitted to use or redistribute
 * this source code without explicit permission.
 *
 *)

unit uCEFFMXWorkScheduler;

{$IFNDEF CPUX64}{$ALIGN ON}{$ENDIF}
{$MINENUMSIZE 4}

{$I cef.inc}

// Define this conditional to use TCEFWorkSchedulerQueueThread instead of using
// PostMessage, Application.QueueAsyncCall or TThread.Queue inside
// TCEFWorkScheduler.ScheduleMessagePumpWork
// TCEFWorkSchedulerQueueThread is just a new experimental way to handle the
// external message pump events for all platforms.
{.$DEFINE USEQUEUETHREAD}

interface

uses
  System.Classes, System.Types,
  FMX.Types, FMX.Controls,
  uCEFConstants, {$IFDEF USEQUEUETHREAD}uCEFWorkSchedulerQueueThread,{$ENDIF} uCEFWorkSchedulerThread;

type
  {$IFNDEF FPC}{$IFDEF DELPHI16_UP}[ComponentPlatformsAttribute(pidWin32 or pidWin64)]{$ENDIF}{$ENDIF}

  TFMXWorkScheduler = class(TComponent)
    protected
      FThread             : TCEFWorkSchedulerThread;
      {$IFDEF USEQUEUETHREAD}
      FQueueThread        : TCEFWorkSchedulerQueueThread;
      {$ENDIF}
      FDepleteWorkCycles  : cardinal;
      FDepleteWorkDelay   : cardinal;
      FDefaultInterval    : integer;
      FStopped            : boolean;
      {$IFDEF MSWINDOWS}
      {$WARN SYMBOL_PLATFORM OFF}
      FPriority           : TThreadPriority;
      {$WARN SYMBOL_PLATFORM ON}
      {$ENDIF}

      {$IFDEF USEQUEUETHREAD}
      procedure CreateQueueThread;
      procedure DestroyQueueThread;
      procedure QueueThread_OnPulse(Sender : TObject; aDelay : integer);
      {$ENDIF}

      procedure DestroyThread;
      procedure DepleteWork;
      procedure NextPulse(aInterval : integer);
      procedure DoWork;
      procedure DoMessageLoopWork;
      procedure Initialize;

      procedure SetDefaultInterval(aValue : integer);
      {$IFDEF MSWINDOWS}
      {$WARN SYMBOL_PLATFORM OFF}
      procedure SetPriority(aValue : TThreadPriority);
      {$WARN SYMBOL_PLATFORM ON}
      {$ENDIF}

      procedure Thread_OnPulse(Sender : TObject);

    public
      constructor Create(AOwner: TComponent); override;
      constructor CreateDelayed;
      destructor  Destroy; override;
      procedure   ScheduleMessagePumpWork(const delay_ms : int64);
      procedure   StopScheduler;
      procedure   ScheduleWork(const delay_ms : int64);
      procedure   CreateThread;

    published
      {$IFDEF MSWINDOWS}
      {$WARN SYMBOL_PLATFORM OFF}
      property    Priority           : TThreadPriority  read FPriority            write SetPriority         default  tpNormal;
      {$WARN SYMBOL_PLATFORM ON}
      {$ENDIF}
      property    DefaultInterval    : integer          read FDefaultInterval     write SetDefaultInterval  default  CEF_TIMER_MAXDELAY;
      property    DepleteWorkCycles  : cardinal         read FDepleteWorkCycles   write FDepleteWorkCycles  default  CEF_TIMER_DEPLETEWORK_CYCLES;
      property    DepleteWorkDelay   : cardinal         read FDepleteWorkDelay    write FDepleteWorkDelay   default  CEF_TIMER_DEPLETEWORK_DELAY;
  end;

var
  GlobalFMXWorkScheduler : TFMXWorkScheduler = nil;

procedure DestroyGlobalFMXWorkScheduler;

implementation

uses
  {$IFDEF MSWINDOWS}WinApi.Windows,{$ENDIF} System.SysUtils, System.Math,
  FMX.Platform, {$IFDEF MSWINDOWS}FMX.Platform.Win,{$ENDIF} FMX.Forms,
  uCEFMiscFunctions, uCEFApplicationCore;

procedure DestroyGlobalFMXWorkScheduler;
begin
  if (GlobalFMXWorkScheduler <> nil) then FreeAndNil(GlobalFMXWorkScheduler);
end;

constructor TFMXWorkScheduler.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  Initialize;

  if not(csDesigning in ComponentState) then CreateThread;
end;

constructor TFMXWorkScheduler.CreateDelayed;
begin
  inherited Create(nil);

  Initialize;
end;

destructor TFMXWorkScheduler.Destroy;
begin
  DestroyThread;
  {$IFDEF USEQUEUETHREAD}
  DestroyQueueThread;
  {$ENDIF}

  inherited Destroy;
end;

procedure TFMXWorkScheduler.Initialize;
begin
  FThread             := nil;
  {$IFDEF USEQUEUETHREAD}
  FQueueThread        := nil;
  {$ENDIF}
  FStopped            := False;
  {$IFDEF MSWINDOWS}
  {$WARN SYMBOL_PLATFORM OFF}
  FPriority           := tpNormal;
  {$WARN SYMBOL_PLATFORM ON}
  {$ENDIF}
  FDefaultInterval    := CEF_TIMER_MAXDELAY;
  FDepleteWorkCycles  := CEF_TIMER_DEPLETEWORK_CYCLES;
  FDepleteWorkDelay   := CEF_TIMER_DEPLETEWORK_DELAY;
end;

procedure TFMXWorkScheduler.CreateThread;
begin
  if (FThread <> nil) then exit;

  FThread                 := TCEFWorkSchedulerThread.Create;
  {$IFDEF MSWINDOWS}
  FThread.Priority        := FPriority;
  {$ENDIF}
  FThread.DefaultInterval := FDefaultInterval;
  FThread.OnPulse         := Thread_OnPulse;
  FThread.Start;

  {$IFDEF USEQUEUETHREAD}
  CreateQueueThread;
  {$ENDIF}
end;

{$IFDEF USEQUEUETHREAD}
procedure TFMXWorkScheduler.CreateQueueThread;
begin
  FQueueThread         := TCEFWorkSchedulerQueueThread.Create;
  FQueueThread.OnPulse := {$IFDEF FPC}@{$ENDIF}QueueThread_OnPulse;
  {$IFDEF DELPHI14_UP}
  FQueueThread.Start;
  {$ELSE}
  {$IFNDEF FPC}
  FQueueThread.Resume;
  {$ELSE}
  FQueueThread.Start;
  {$ENDIF}
  {$ENDIF}
end;

procedure TFMXWorkScheduler.DestroyQueueThread;
begin
  try
    if (FQueueThread <> nil) then
      begin
        FQueueThread.Terminate;
        FQueueThread.StopThread;
        FQueueThread.WaitFor;
        FreeAndNil(FQueueThread);
      end;
  except
    on e : exception do
      if CustomExceptionHandler('TFMXWorkScheduler.DestroyQueueThread', e) then raise;
  end;
end;

procedure TFMXWorkScheduler.QueueThread_OnPulse(Sender : TObject; aDelay : integer);
begin
  ScheduleWork(aDelay);
end;
{$ENDIF}

procedure TFMXWorkScheduler.DestroyThread;
begin
  try
    if (FThread <> nil) then
      begin
        FThread.Terminate;
        FThread.NextPulse(0);
        FThread.WaitFor;
        FreeAndNil(FThread);
      end;
  except
    on e : exception do
      if CustomExceptionHandler('TFMXWorkScheduler.DestroyThread', e) then raise;
  end;
end;

procedure TFMXWorkScheduler.DoMessageLoopWork;
begin
  if (GlobalCEFApp <> nil) then GlobalCEFApp.DoMessageLoopWork;
end;

procedure TFMXWorkScheduler.SetDefaultInterval(aValue : integer);
begin
  FDefaultInterval := aValue;
  if (FThread <> nil) then FThread.DefaultInterval := aValue;
end;

{$IFDEF MSWINDOWS}
{$WARN SYMBOL_PLATFORM OFF}
procedure TFMXWorkScheduler.SetPriority(aValue : TThreadPriority);
begin
  FPriority := aValue;
  if (FThread <> nil) then FThread.Priority := aValue;
end;
{$WARN SYMBOL_PLATFORM ON}
{$ENDIF}

procedure TFMXWorkScheduler.DepleteWork;
var
  i : cardinal;
begin
  i := FDepleteWorkCycles;

  while (i > 0) do
    begin
      DoMessageLoopWork;
      Sleep(FDepleteWorkDelay);
      dec(i);
    end;
end;

procedure TFMXWorkScheduler.ScheduleMessagePumpWork(const delay_ms : int64);
begin
  if FStopped then exit;

  {$IFDEF USEQUEUETHREAD}
    if (FQueueThread <> nil) and FQueueThread.Ready then
      begin
        FQueueThread.EnqueueValue(integer(delay_ms));
        exit;
      end;
  {$ENDIF}

  TThread.Queue(nil, procedure
                     begin
                       ScheduleWork(delay_ms);
                     end);
end;

procedure TFMXWorkScheduler.StopScheduler;
begin
  FStopped := True;
  NextPulse(0);
  DepleteWork;
end;

procedure TFMXWorkScheduler.Thread_OnPulse(Sender: TObject);
begin
  if not(FStopped) then DoMessageLoopWork;
end;

procedure TFMXWorkScheduler.DoWork;
begin
  DoMessageLoopWork;
  NextPulse(FDefaultInterval);
end;

procedure TFMXWorkScheduler.ScheduleWork(const delay_ms : int64);
begin
  if not(FStopped) then
    begin
      if (delay_ms <= 0) then
        DoWork
       else
        NextPulse(delay_ms);
    end;
end;

procedure TFMXWorkScheduler.NextPulse(aInterval : integer);
begin
  if (FThread <> nil) then FThread.NextPulse(aInterval);
end;

end.
